github.com/minio/madmin-go/v3@v3.0.51/policy-commands.go (about)

     1  //
     2  // Copyright (c) 2015-2022 MinIO, Inc.
     3  //
     4  // This file is part of MinIO Object Storage stack
     5  //
     6  // This program is free software: you can redistribute it and/or modify
     7  // it under the terms of the GNU Affero General Public License as
     8  // published by the Free Software Foundation, either version 3 of the
     9  // License, or (at your option) any later version.
    10  //
    11  // This program is distributed in the hope that it will be useful,
    12  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    13  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    14  // GNU Affero General Public License for more details.
    15  //
    16  // You should have received a copy of the GNU Affero General Public License
    17  // along with this program. If not, see <http://www.gnu.org/licenses/>.
    18  //
    19  
    20  package madmin
    21  
    22  import (
    23  	"context"
    24  	"encoding/json"
    25  	"io"
    26  	"net/http"
    27  	"net/url"
    28  	"time"
    29  )
    30  
    31  // InfoCannedPolicy - expand canned policy into JSON structure.
    32  //
    33  // Deprecated: Use InfoCannedPolicyV2 instead.
    34  func (adm *AdminClient) InfoCannedPolicy(ctx context.Context, policyName string) ([]byte, error) {
    35  	queryValues := url.Values{}
    36  	queryValues.Set("name", policyName)
    37  
    38  	reqData := requestData{
    39  		relPath:     adminAPIPrefix + "/info-canned-policy",
    40  		queryValues: queryValues,
    41  	}
    42  
    43  	// Execute GET on /minio/admin/v3/info-canned-policy
    44  	resp, err := adm.executeMethod(ctx, http.MethodGet, reqData)
    45  
    46  	defer closeResponse(resp)
    47  	if err != nil {
    48  		return nil, err
    49  	}
    50  
    51  	if resp.StatusCode != http.StatusOK {
    52  		return nil, httpRespToErrorResponse(resp)
    53  	}
    54  
    55  	return io.ReadAll(resp.Body)
    56  }
    57  
    58  // PolicyInfo contains information on a policy.
    59  type PolicyInfo struct {
    60  	PolicyName string
    61  	Policy     json.RawMessage
    62  	CreateDate time.Time `json:",omitempty"`
    63  	UpdateDate time.Time `json:",omitempty"`
    64  }
    65  
    66  // MarshalJSON marshaller for JSON
    67  func (pi PolicyInfo) MarshalJSON() ([]byte, error) {
    68  	type aliasPolicyInfo PolicyInfo // needed to avoid recursive marshal
    69  	if pi.CreateDate.IsZero() && pi.UpdateDate.IsZero() {
    70  		return json.Marshal(&struct {
    71  			PolicyName string
    72  			Policy     json.RawMessage
    73  		}{
    74  			PolicyName: pi.PolicyName,
    75  			Policy:     pi.Policy,
    76  		})
    77  	}
    78  	return json.Marshal(aliasPolicyInfo(pi))
    79  }
    80  
    81  // InfoCannedPolicyV2 - get info on a policy including timestamps and policy json.
    82  func (adm *AdminClient) InfoCannedPolicyV2(ctx context.Context, policyName string) (*PolicyInfo, error) {
    83  	queryValues := url.Values{}
    84  	queryValues.Set("name", policyName)
    85  	queryValues.Set("v", "2")
    86  
    87  	reqData := requestData{
    88  		relPath:     adminAPIPrefix + "/info-canned-policy",
    89  		queryValues: queryValues,
    90  	}
    91  
    92  	// Execute GET on /minio/admin/v3/info-canned-policy
    93  	resp, err := adm.executeMethod(ctx, http.MethodGet, reqData)
    94  
    95  	defer closeResponse(resp)
    96  	if err != nil {
    97  		return nil, err
    98  	}
    99  
   100  	if resp.StatusCode != http.StatusOK {
   101  		return nil, httpRespToErrorResponse(resp)
   102  	}
   103  
   104  	data, err := io.ReadAll(resp.Body)
   105  	if err != nil {
   106  		return nil, err
   107  	}
   108  
   109  	var p PolicyInfo
   110  	err = json.Unmarshal(data, &p)
   111  	return &p, err
   112  }
   113  
   114  // ListCannedPolicies - list all configured canned policies.
   115  func (adm *AdminClient) ListCannedPolicies(ctx context.Context) (map[string]json.RawMessage, error) {
   116  	reqData := requestData{
   117  		relPath: adminAPIPrefix + "/list-canned-policies",
   118  	}
   119  
   120  	// Execute GET on /minio/admin/v3/list-canned-policies
   121  	resp, err := adm.executeMethod(ctx, http.MethodGet, reqData)
   122  
   123  	defer closeResponse(resp)
   124  	if err != nil {
   125  		return nil, err
   126  	}
   127  
   128  	if resp.StatusCode != http.StatusOK {
   129  		return nil, httpRespToErrorResponse(resp)
   130  	}
   131  
   132  	respBytes, err := io.ReadAll(resp.Body)
   133  	if err != nil {
   134  		return nil, err
   135  	}
   136  
   137  	policies := make(map[string]json.RawMessage)
   138  	if err = json.Unmarshal(respBytes, &policies); err != nil {
   139  		return nil, err
   140  	}
   141  
   142  	return policies, nil
   143  }
   144  
   145  // RemoveCannedPolicy - remove a policy for a canned.
   146  func (adm *AdminClient) RemoveCannedPolicy(ctx context.Context, policyName string) error {
   147  	queryValues := url.Values{}
   148  	queryValues.Set("name", policyName)
   149  
   150  	reqData := requestData{
   151  		relPath:     adminAPIPrefix + "/remove-canned-policy",
   152  		queryValues: queryValues,
   153  	}
   154  
   155  	// Execute DELETE on /minio/admin/v3/remove-canned-policy to remove policy.
   156  	resp, err := adm.executeMethod(ctx, http.MethodDelete, reqData)
   157  
   158  	defer closeResponse(resp)
   159  	if err != nil {
   160  		return err
   161  	}
   162  
   163  	if resp.StatusCode != http.StatusOK {
   164  		return httpRespToErrorResponse(resp)
   165  	}
   166  
   167  	return nil
   168  }
   169  
   170  // AddCannedPolicy - adds a policy for a canned.
   171  func (adm *AdminClient) AddCannedPolicy(ctx context.Context, policyName string, policy []byte) error {
   172  	if policy == nil {
   173  		return ErrInvalidArgument("policy input cannot be empty")
   174  	}
   175  
   176  	queryValues := url.Values{}
   177  	queryValues.Set("name", policyName)
   178  
   179  	reqData := requestData{
   180  		relPath:     adminAPIPrefix + "/add-canned-policy",
   181  		queryValues: queryValues,
   182  		content:     policy,
   183  	}
   184  
   185  	// Execute PUT on /minio/admin/v3/add-canned-policy to set policy.
   186  	resp, err := adm.executeMethod(ctx, http.MethodPut, reqData)
   187  
   188  	defer closeResponse(resp)
   189  	if err != nil {
   190  		return err
   191  	}
   192  
   193  	if resp.StatusCode != http.StatusOK {
   194  		return httpRespToErrorResponse(resp)
   195  	}
   196  
   197  	return nil
   198  }
   199  
   200  // SetPolicy - sets the policy for a user or a group.
   201  //
   202  // Deprecated: Use AttachPolicy/DetachPolicy to update builtin user policies
   203  // instead. Use AttachPolicyLDAP/DetachPolicyLDAP to update LDAP user policies.
   204  // This function and the corresponding server API will be removed in future
   205  // releases.
   206  func (adm *AdminClient) SetPolicy(ctx context.Context, policyName, entityName string, isGroup bool) error {
   207  	queryValues := url.Values{}
   208  	queryValues.Set("policyName", policyName)
   209  	queryValues.Set("userOrGroup", entityName)
   210  	groupStr := "false"
   211  	if isGroup {
   212  		groupStr = "true"
   213  	}
   214  	queryValues.Set("isGroup", groupStr)
   215  
   216  	reqData := requestData{
   217  		relPath:     adminAPIPrefix + "/set-user-or-group-policy",
   218  		queryValues: queryValues,
   219  	}
   220  
   221  	// Execute PUT on /minio/admin/v3/set-user-or-group-policy to set policy.
   222  	resp, err := adm.executeMethod(ctx, http.MethodPut, reqData)
   223  	defer closeResponse(resp)
   224  	if err != nil {
   225  		return err
   226  	}
   227  
   228  	if resp.StatusCode != http.StatusOK {
   229  		return httpRespToErrorResponse(resp)
   230  	}
   231  	return nil
   232  }
   233  
   234  func (adm *AdminClient) attachOrDetachPolicyBuiltin(ctx context.Context, isAttach bool,
   235  	r PolicyAssociationReq,
   236  ) (PolicyAssociationResp, error) {
   237  	err := r.IsValid()
   238  	if err != nil {
   239  		return PolicyAssociationResp{}, err
   240  	}
   241  
   242  	plainBytes, err := json.Marshal(r)
   243  	if err != nil {
   244  		return PolicyAssociationResp{}, err
   245  	}
   246  
   247  	encBytes, err := EncryptData(adm.getSecretKey(), plainBytes)
   248  	if err != nil {
   249  		return PolicyAssociationResp{}, err
   250  	}
   251  
   252  	suffix := "detach"
   253  	if isAttach {
   254  		suffix = "attach"
   255  	}
   256  	h := make(http.Header, 1)
   257  	h.Add("Content-Type", "application/octet-stream")
   258  	reqData := requestData{
   259  		customHeaders: h,
   260  		relPath:       adminAPIPrefix + "/idp/builtin/policy/" + suffix,
   261  		content:       encBytes,
   262  	}
   263  
   264  	resp, err := adm.executeMethod(ctx, http.MethodPost, reqData)
   265  	defer closeResponse(resp)
   266  	if err != nil {
   267  		return PolicyAssociationResp{}, err
   268  	}
   269  
   270  	// Older minio does not send a response, so we handle that case.
   271  
   272  	switch {
   273  	case resp.StatusCode == http.StatusOK:
   274  		// Newer/current minio sends a result.
   275  		content, err := DecryptData(adm.getSecretKey(), resp.Body)
   276  		if err != nil {
   277  			return PolicyAssociationResp{}, err
   278  		}
   279  
   280  		rsp := PolicyAssociationResp{}
   281  		err = json.Unmarshal(content, &rsp)
   282  		return rsp, err
   283  
   284  	case resp.StatusCode == http.StatusCreated || resp.StatusCode == http.StatusNoContent:
   285  		// Older minio - no result sent. TODO(aditya): Remove this case after
   286  		// newer minio is released.
   287  		return PolicyAssociationResp{}, nil
   288  
   289  	default:
   290  		// Error response case.
   291  		return PolicyAssociationResp{}, httpRespToErrorResponse(resp)
   292  	}
   293  }
   294  
   295  // AttachPolicy - attach policies to a user or group.
   296  func (adm *AdminClient) AttachPolicy(ctx context.Context, r PolicyAssociationReq) (PolicyAssociationResp, error) {
   297  	return adm.attachOrDetachPolicyBuiltin(ctx, true, r)
   298  }
   299  
   300  // DetachPolicy - detach policies from a user or group.
   301  func (adm *AdminClient) DetachPolicy(ctx context.Context, r PolicyAssociationReq) (PolicyAssociationResp, error) {
   302  	return adm.attachOrDetachPolicyBuiltin(ctx, false, r)
   303  }
   304  
   305  // GetPolicyEntities - returns builtin policy entities.
   306  func (adm *AdminClient) GetPolicyEntities(ctx context.Context, q PolicyEntitiesQuery) (r PolicyEntitiesResult, err error) {
   307  	params := make(url.Values)
   308  	params["user"] = q.Users
   309  	params["group"] = q.Groups
   310  	params["policy"] = q.Policy
   311  
   312  	reqData := requestData{
   313  		relPath:     adminAPIPrefix + "/idp/builtin/policy-entities",
   314  		queryValues: params,
   315  	}
   316  
   317  	resp, err := adm.executeMethod(ctx, http.MethodGet, reqData)
   318  	defer closeResponse(resp)
   319  	if err != nil {
   320  		return r, err
   321  	}
   322  
   323  	if resp.StatusCode != http.StatusOK {
   324  		return r, httpRespToErrorResponse(resp)
   325  	}
   326  
   327  	content, err := DecryptData(adm.getSecretKey(), resp.Body)
   328  	if err != nil {
   329  		return r, err
   330  	}
   331  
   332  	err = json.Unmarshal(content, &r)
   333  	return r, err
   334  }