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 }