storj.io/minio@v0.0.0-20230509071714-0cbc90f649b1/cmd/config/policy/opa/config.go (about) 1 /* 2 * MinIO Cloud Storage, (C) 2018-2019 MinIO, Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package opa 18 19 import ( 20 "bytes" 21 "encoding/json" 22 "io" 23 "io/ioutil" 24 "net/http" 25 26 "storj.io/minio/cmd/config" 27 "storj.io/minio/pkg/env" 28 iampolicy "storj.io/minio/pkg/iam/policy" 29 xnet "storj.io/minio/pkg/net" 30 ) 31 32 // Env IAM OPA URL 33 const ( 34 URL = "url" 35 AuthToken = "auth_token" 36 37 EnvPolicyOpaURL = "MINIO_POLICY_OPA_URL" 38 EnvPolicyOpaAuthToken = "MINIO_POLICY_OPA_AUTH_TOKEN" 39 ) 40 41 // DefaultKVS - default config for OPA config 42 var ( 43 DefaultKVS = config.KVS{ 44 config.KV{ 45 Key: URL, 46 Value: "", 47 }, 48 config.KV{ 49 Key: AuthToken, 50 Value: "", 51 }, 52 } 53 ) 54 55 // Args opa general purpose policy engine configuration. 56 type Args struct { 57 URL *xnet.URL `json:"url"` 58 AuthToken string `json:"authToken"` 59 Transport http.RoundTripper `json:"-"` 60 CloseRespFn func(r io.ReadCloser) `json:"-"` 61 } 62 63 // Validate - validate opa configuration params. 64 func (a *Args) Validate() error { 65 req, err := http.NewRequest(http.MethodPost, a.URL.String(), bytes.NewReader([]byte(""))) 66 if err != nil { 67 return err 68 } 69 70 req.Header.Set("Content-Type", "application/json") 71 if a.AuthToken != "" { 72 req.Header.Set("Authorization", a.AuthToken) 73 } 74 75 client := &http.Client{Transport: a.Transport} 76 resp, err := client.Do(req) 77 if err != nil { 78 return err 79 } 80 defer a.CloseRespFn(resp.Body) 81 82 return nil 83 } 84 85 // UnmarshalJSON - decodes JSON data. 86 func (a *Args) UnmarshalJSON(data []byte) error { 87 // subtype to avoid recursive call to UnmarshalJSON() 88 type subArgs Args 89 var so subArgs 90 91 if err := json.Unmarshal(data, &so); err != nil { 92 return err 93 } 94 95 oa := Args(so) 96 if oa.URL == nil || oa.URL.String() == "" { 97 *a = oa 98 return nil 99 } 100 101 *a = oa 102 return nil 103 } 104 105 // Opa - implements opa policy agent calls. 106 type Opa struct { 107 args Args 108 client *http.Client 109 } 110 111 // Enabled returns if opa is enabled. 112 func Enabled(kvs config.KVS) bool { 113 return kvs.Get(URL) != "" 114 } 115 116 // LookupConfig lookup Opa from config, override with any ENVs. 117 func LookupConfig(kv config.KVS, transport *http.Transport, closeRespFn func(io.ReadCloser)) (Args, error) { 118 args := Args{} 119 120 if err := config.CheckValidKeys(config.PolicyOPASubSys, kv, DefaultKVS); err != nil { 121 return args, err 122 } 123 124 opaURL := env.Get(EnvIamOpaURL, "") 125 if opaURL == "" { 126 opaURL = env.Get(EnvPolicyOpaURL, kv.Get(URL)) 127 if opaURL == "" { 128 return args, nil 129 } 130 } 131 authToken := env.Get(EnvIamOpaAuthToken, "") 132 if authToken == "" { 133 authToken = env.Get(EnvPolicyOpaAuthToken, kv.Get(AuthToken)) 134 } 135 136 u, err := xnet.ParseHTTPURL(opaURL) 137 if err != nil { 138 return args, err 139 } 140 args = Args{ 141 URL: u, 142 AuthToken: authToken, 143 Transport: transport, 144 CloseRespFn: closeRespFn, 145 } 146 if err = args.Validate(); err != nil { 147 return args, err 148 } 149 return args, nil 150 } 151 152 // New - initializes opa policy engine connector. 153 func New(args Args) *Opa { 154 // No opa args. 155 if args.URL == nil || args.URL.Scheme == "" && args.AuthToken == "" { 156 return nil 157 } 158 return &Opa{ 159 args: args, 160 client: &http.Client{Transport: args.Transport}, 161 } 162 } 163 164 // IsAllowed - checks given policy args is allowed to continue the REST API. 165 func (o *Opa) IsAllowed(args iampolicy.Args) (bool, error) { 166 if o == nil { 167 return false, nil 168 } 169 170 // OPA input 171 body := make(map[string]interface{}) 172 body["input"] = args 173 174 inputBytes, err := json.Marshal(body) 175 if err != nil { 176 return false, err 177 } 178 179 req, err := http.NewRequest(http.MethodPost, o.args.URL.String(), bytes.NewReader(inputBytes)) 180 if err != nil { 181 return false, err 182 } 183 184 req.Header.Set("Content-Type", "application/json") 185 if o.args.AuthToken != "" { 186 req.Header.Set("Authorization", o.args.AuthToken) 187 } 188 189 resp, err := o.client.Do(req) 190 if err != nil { 191 return false, err 192 } 193 defer o.args.CloseRespFn(resp.Body) 194 195 // Read the body to be saved later. 196 opaRespBytes, err := ioutil.ReadAll(resp.Body) 197 if err != nil { 198 return false, err 199 } 200 201 // Handle large OPA responses when OPA URL is of 202 // form http://localhost:8181/v1/data/httpapi/authz 203 type opaResultAllow struct { 204 Result struct { 205 Allow bool `json:"allow"` 206 } `json:"result"` 207 } 208 209 // Handle simpler OPA responses when OPA URL is of 210 // form http://localhost:8181/v1/data/httpapi/authz/allow 211 type opaResult struct { 212 Result bool `json:"result"` 213 } 214 215 respBody := bytes.NewReader(opaRespBytes) 216 217 var result opaResult 218 if err = json.NewDecoder(respBody).Decode(&result); err != nil { 219 respBody.Seek(0, 0) 220 var resultAllow opaResultAllow 221 if err = json.NewDecoder(respBody).Decode(&resultAllow); err != nil { 222 return false, err 223 } 224 return resultAllow.Result.Allow, nil 225 } 226 227 return result.Result, nil 228 }