github.com/minio/minio@v0.0.0-20240328213742-3f72439b8a27/internal/config/policy/plugin/config.go (about) 1 // Copyright (c) 2015-2022 MinIO, Inc. 2 // 3 // This file is part of MinIO Object Storage stack 4 // 5 // This program is free software: you can redistribute it and/or modify 6 // it under the terms of the GNU Affero General Public License as published by 7 // the Free Software Foundation, either version 3 of the License, or 8 // (at your option) any later version. 9 // 10 // This program is distributed in the hope that it will be useful 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 // GNU Affero General Public License for more details. 14 // 15 // You should have received a copy of the GNU Affero General Public License 16 // along with this program. If not, see <http://www.gnu.org/licenses/>. 17 18 package plugin 19 20 import ( 21 "bytes" 22 "encoding/json" 23 "io" 24 "net/http" 25 "time" 26 27 "github.com/minio/minio/internal/config" 28 xhttp "github.com/minio/minio/internal/http" 29 xnet "github.com/minio/pkg/v2/net" 30 "github.com/minio/pkg/v2/policy" 31 ) 32 33 // Authorization Plugin config and env variables 34 const ( 35 URL = "url" 36 AuthToken = "auth_token" 37 EnableHTTP2 = "enable_http2" 38 39 EnvPolicyPluginURL = "MINIO_POLICY_PLUGIN_URL" 40 EnvPolicyPluginAuthToken = "MINIO_POLICY_PLUGIN_AUTH_TOKEN" 41 EnvPolicyPluginEnableHTTP2 = "MINIO_POLICY_PLUGIN_ENABLE_HTTP2" 42 ) 43 44 // DefaultKVS - default config for Authz plugin config 45 var ( 46 DefaultKVS = config.KVS{ 47 config.KV{ 48 Key: URL, 49 Value: "", 50 }, 51 config.KV{ 52 Key: AuthToken, 53 Value: "", 54 }, 55 config.KV{ 56 Key: EnableHTTP2, 57 Value: "off", 58 }, 59 } 60 ) 61 62 // Args for general purpose policy engine configuration. 63 type Args struct { 64 URL *xnet.URL `json:"url"` 65 AuthToken string `json:"authToken"` 66 Transport http.RoundTripper `json:"-"` 67 CloseRespFn func(r io.ReadCloser) `json:"-"` 68 } 69 70 // Validate - validate opa configuration params. 71 func (a *Args) Validate() error { 72 req, err := http.NewRequest(http.MethodPost, a.URL.String(), bytes.NewReader([]byte(""))) 73 if err != nil { 74 return err 75 } 76 77 req.Header.Set("Content-Type", "application/json") 78 if a.AuthToken != "" { 79 req.Header.Set("Authorization", a.AuthToken) 80 } 81 82 client := &http.Client{Transport: a.Transport} 83 resp, err := client.Do(req) 84 if err != nil { 85 return err 86 } 87 defer a.CloseRespFn(resp.Body) 88 89 return nil 90 } 91 92 // UnmarshalJSON - decodes JSON data. 93 func (a *Args) UnmarshalJSON(data []byte) error { 94 // subtype to avoid recursive call to UnmarshalJSON() 95 type subArgs Args 96 var so subArgs 97 98 if err := json.Unmarshal(data, &so); err != nil { 99 return err 100 } 101 102 oa := Args(so) 103 if oa.URL == nil || oa.URL.String() == "" { 104 *a = oa 105 return nil 106 } 107 108 *a = oa 109 return nil 110 } 111 112 // AuthZPlugin - implements opa policy agent calls. 113 type AuthZPlugin struct { 114 args Args 115 client *http.Client 116 } 117 118 // Enabled returns if AuthZPlugin is enabled. 119 func Enabled(kvs config.KVS) bool { 120 return kvs.Get(URL) != "" 121 } 122 123 // LookupConfig lookup AuthZPlugin from config, override with any ENVs. 124 func LookupConfig(s config.Config, httpSettings xhttp.ConnSettings, closeRespFn func(io.ReadCloser)) (Args, error) { 125 args := Args{} 126 127 if err := s.CheckValidKeys(config.PolicyPluginSubSys, nil); err != nil { 128 return args, err 129 } 130 131 getCfg := func(cfgParam string) string { 132 // As parameters are already validated, we skip checking 133 // if the config param was found. 134 val, _, _ := s.ResolveConfigParam(config.PolicyPluginSubSys, config.Default, cfgParam, false) 135 return val 136 } 137 138 pluginURL := getCfg(URL) 139 if pluginURL == "" { 140 return args, nil 141 } 142 143 u, err := xnet.ParseHTTPURL(pluginURL) 144 if err != nil { 145 return args, err 146 } 147 148 enableHTTP2 := false 149 if v := getCfg(EnableHTTP2); v != "" { 150 enableHTTP2, err = config.ParseBool(v) 151 if err != nil { 152 return args, err 153 } 154 } 155 httpSettings.EnableHTTP2 = enableHTTP2 156 transport := httpSettings.NewHTTPTransportWithTimeout(time.Minute) 157 158 args = Args{ 159 URL: u, 160 AuthToken: getCfg(AuthToken), 161 Transport: transport, 162 CloseRespFn: closeRespFn, 163 } 164 if err = args.Validate(); err != nil { 165 return args, err 166 } 167 return args, nil 168 } 169 170 // New - initializes Authorization Management Plugin. 171 func New(args Args) *AuthZPlugin { 172 if args.URL == nil || args.URL.Scheme == "" && args.AuthToken == "" { 173 return nil 174 } 175 return &AuthZPlugin{ 176 args: args, 177 client: &http.Client{Transport: args.Transport}, 178 } 179 } 180 181 // IsAllowed - checks given policy args is allowed to continue the REST API. 182 func (o *AuthZPlugin) IsAllowed(args policy.Args) (bool, error) { 183 if o == nil { 184 return false, nil 185 } 186 187 // Access Management Plugin Input 188 body := make(map[string]interface{}) 189 body["input"] = args 190 191 inputBytes, err := json.Marshal(body) 192 if err != nil { 193 return false, err 194 } 195 196 req, err := http.NewRequest(http.MethodPost, o.args.URL.String(), bytes.NewReader(inputBytes)) 197 if err != nil { 198 return false, err 199 } 200 201 req.Header.Set("Content-Type", "application/json") 202 if o.args.AuthToken != "" { 203 req.Header.Set("Authorization", o.args.AuthToken) 204 } 205 206 resp, err := o.client.Do(req) 207 if err != nil { 208 return false, err 209 } 210 defer o.args.CloseRespFn(resp.Body) 211 212 // Read the body to be saved later. 213 opaRespBytes, err := io.ReadAll(resp.Body) 214 if err != nil { 215 return false, err 216 } 217 218 // Handle large OPA responses when OPA URL is of 219 // form http://localhost:8181/v1/data/httpapi/authz 220 type opaResultAllow struct { 221 Result struct { 222 Allow bool `json:"allow"` 223 } `json:"result"` 224 } 225 226 // Handle simpler OPA responses when OPA URL is of 227 // form http://localhost:8181/v1/data/httpapi/authz/allow 228 type opaResult struct { 229 Result bool `json:"result"` 230 } 231 232 respBody := bytes.NewReader(opaRespBytes) 233 234 var result opaResult 235 if err = json.NewDecoder(respBody).Decode(&result); err != nil { 236 respBody.Seek(0, 0) 237 var resultAllow opaResultAllow 238 if err = json.NewDecoder(respBody).Decode(&resultAllow); err != nil { 239 return false, err 240 } 241 return resultAllow.Result.Allow, nil 242 } 243 244 return result.Result, nil 245 }