1/* 2** Zabbix 3** Copyright (C) 2001-2021 Zabbix SIA 4** 5** This program is free software; you can redistribute it and/or modify 6** it under the terms of the GNU General Public License as published by 7** the Free Software Foundation; either version 2 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 General Public License for more details. 14** 15** You should have received a copy of the GNU General Public License 16** along with this program; if not, write to the Free Software 17** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 18**/ 19 20package keyaccess 21 22import ( 23 "errors" 24 "fmt" 25 "math" 26 "sort" 27 28 "zabbix.com/pkg/conf" 29 "zabbix.com/pkg/itemutil" 30 "zabbix.com/pkg/log" 31 "zabbix.com/pkg/wildcard" 32) 33 34// RuleType Access rule permission type 35type RuleType int 36 37// Rule access types 38const ( 39 ALLOW RuleType = iota 40 DENY 41) 42 43func (t RuleType) String() string { 44 switch t { 45 case ALLOW: 46 return "AllowKey" 47 case DENY: 48 return "DenyKey" 49 default: 50 return "unknown" 51 } 52} 53 54// Record key access record 55type Record struct { 56 Pattern string 57 Permission RuleType 58 Line int 59} 60 61// Rule key access rule definition 62type Rule struct { 63 Pattern string 64 Permission RuleType 65 Key string 66 Params []string 67} 68 69var rules []*Rule 70 71func parse(rec Record) (r *Rule, err error) { 72 r = &Rule{ 73 Permission: rec.Permission, 74 Pattern: rec.Pattern, 75 } 76 77 if r.Key, r.Params, err = itemutil.ParseWildcardKey(rec.Pattern); err != nil { 78 return nil, err 79 } 80 81 r.Key = wildcard.Minimize(r.Key) 82 83 for i := range r.Params { 84 r.Params[i] = wildcard.Minimize(r.Params[i]) 85 } 86 // remove repeated trailing "*" parameters 87 var n int = 0 88 for i := len(r.Params) - 1; i >= 0; i-- { 89 if r.Params[i] == "*" { 90 n++ 91 } 92 } 93 if n > 1 { 94 r.Params = r.Params[:len(r.Params)-n+1] 95 } 96 97 return r, nil 98} 99 100func findRule(proto *Rule) (rule *Rule, index int) { 101 for j, r := range rules { 102 if proto.Key != r.Key || len(proto.Params) != len(r.Params) { 103 continue 104 } 105 for i, p := range proto.Params { 106 if p != r.Params[i] { 107 goto noMatch 108 } 109 } 110 return r, j 111 noMatch: 112 } 113 return 114} 115 116func addRule(rec Record) (err error) { 117 var rule *Rule 118 119 if rule, err = parse(rec); err != nil { 120 return 121 } 122 if r, _ := findRule(rule); r != nil { 123 var desc string 124 if r.Permission == rule.Permission { 125 desc = "duplicates" 126 } else { 127 desc = "conflicts" 128 } 129 log.Warningf(`%s access rule "%s" was not added because it %s with another rule defined above`, 130 rec.Permission, rec.Pattern, desc) 131 return 132 } 133 rules = append(rules, rule) 134 return 135} 136 137// GetNumberOfRules returns a number of access rules configured 138func GetNumberOfRules() int { 139 return len(rules) 140} 141 142// LoadRules adds key access records to access rule list 143func LoadRules(allowRecords interface{}, denyRecords interface{}) (err error) { 144 rules = rules[:0] 145 var records []Record 146 sysrunIndex := math.MaxInt32 147 148 // load AllowKey/DenyKey parameters 149 if node, ok := allowRecords.(*conf.Node); ok { 150 for _, v := range node.Nodes { 151 if value, ok := v.(*conf.Value); ok { 152 records = append(records, Record{Pattern: string(value.Value), Permission: ALLOW, Line: value.Line}) 153 } 154 } 155 } 156 if node, ok := denyRecords.(*conf.Node); ok { 157 for _, v := range node.Nodes { 158 if value, ok := v.(*conf.Value); ok { 159 records = append(records, Record{Pattern: string(value.Value), Permission: DENY, Line: value.Line}) 160 } 161 } 162 } 163 164 sort.SliceStable(records, func(i, j int) bool { 165 return records[i].Line < records[j].Line 166 }) 167 168 for _, r := range records { 169 if err = addRule(r); err != nil { 170 err = fmt.Errorf("\"%s\" %s", r.Pattern, err.Error()) 171 return 172 } 173 } 174 175 rulesNum := len(rules) 176 // create system.run[*] deny rule to be appended at the end of rule list unless other 177 // system.run[*] rules are present 178 sysrunRule, err := parse(Record{Pattern: "system.run[*]", Permission: DENY, Line: 0}) 179 if err != nil { 180 return 181 } 182 if r, i := findRule(sysrunRule); r != nil { 183 sysrunIndex = i 184 rulesNum-- 185 } 186 187 if rulesNum != 0 { 188 // remove rules after 'full match' rule 189 for i, r := range rules { 190 if len(r.Params) == 0 && r.Key == "*" { 191 if i < sysrunIndex { 192 sysrunIndex = i 193 } 194 for j := i + 1; j < len(rules); j++ { 195 log.Warningf(`removed unreachable %s "%s" rule`, rules[j].Permission, rules[j].Pattern) 196 } 197 rules = rules[:i+1] 198 break 199 } 200 } 201 202 // remove trailing 'allow' rules 203 cutoff := len(rules) 204 for i := len(rules) - 1; i >= 0; i-- { 205 if rules[i].Permission != ALLOW { 206 break 207 } 208 // system.run allow rules are not redundant because of default system.run[*] deny rule 209 if rules[i].Key != "system.run" { 210 if i != sysrunIndex { 211 log.Warningf(`removed redundant trailing AllowKey "%s" rule`, rules[i].Pattern) 212 } 213 for j := i; j < len(rules)-1; j++ { 214 rules[j] = rules[j+1] 215 } 216 cutoff-- 217 } 218 } 219 rules = rules[:cutoff] 220 221 if len(rules) == 0 { 222 return errors.New("Item key access rules are configured to match all keys," + 223 " indicating possible configuration problem. " + 224 " Please remove the rules if that was the purpose.") 225 } 226 } 227 228 if sysrunIndex == math.MaxInt32 { 229 rules = append(rules, sysrunRule) 230 } 231 232 return nil 233} 234 235// CheckRules checks if specified key and parameters are not restricted by defined rules 236func CheckRules(key string, params []string) (result bool) { 237 result = true 238 239 emptyParams := len(params) == 1 && len(params[0]) == 0 240 241 for _, r := range rules { 242 numParamsRule := len(r.Params) 243 numParams := len(params) 244 245 // match all rule 246 if r.Key == "*" && numParamsRule == 0 { 247 return r.Permission == ALLOW 248 } 249 250 if numParamsRule > 0 { 251 if r.Params[numParamsRule-1] == "*" { 252 if numParamsRule == 1 && numParams == 0 { 253 continue // rule: key[*], request: key 254 } 255 } else { 256 if numParams < numParamsRule { 257 continue // too few parameters 258 } 259 if numParams > numParamsRule { 260 continue // too many params 261 } 262 } 263 } 264 265 if !wildcard.Match(key, r.Key) { 266 continue // key doesn't match 267 } 268 269 if numParamsRule == 0 { 270 if emptyParams { 271 continue // no parameters expected by rule 272 } 273 if numParams == 0 { 274 return r.Permission == ALLOW 275 } 276 } 277 278 for i, p := range r.Params { 279 if i == numParamsRule-1 { // last parameter 280 if p == "*" { 281 return r.Permission == ALLOW // skip next parameter checks 282 } 283 if numParams <= i { 284 break // out of parameters 285 } 286 if !wildcard.Match(params[i], p) { 287 break // parameter doesn't match pattern 288 } 289 return r.Permission == ALLOW 290 } 291 if numParams <= i || !wildcard.Match(params[i], p) { 292 break // parameter doesn't match pattern 293 } 294 } 295 } 296 297 return true // allow by default for backward compatibility 298} 299