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