forked from coredns/policy
/
firewall.go
133 lines (115 loc) · 4.08 KB
/
firewall.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
// Package firewall enables filtering on query and response using direct expression as policy.
// it allows interact with other Policy Engines if those are plugin implementing the Engineer interface
package firewall
import (
"context"
"errors"
"github.com/coredns/coredns/plugin"
"github.com/coredns/coredns/plugin/pkg/log"
"github.com/coredns/coredns/plugin/pkg/nonwriter"
"github.com/coredns/coredns/request"
"github.com/amir-microsoft/policy/plugin/firewall/policy"
"github.com/amir-microsoft/policy/plugin/firewall/rule"
"github.com/amir-microsoft/policy/plugin/pkg/response"
"github.com/miekg/dns"
)
var logger = log.NewWithPlugin("firewall")
var (
errInvalidAction = errors.New("invalid action")
)
// ExpressionEngineName is the name associated with built-in rules of Expression type.
const ExpressionEngineName = "--default--"
// firewall represents a plugin instance that can validate DNS
// requests and replies using rulelists on the query and/or on the reply
type firewall struct {
engines map[string]policy.Engine
query *rule.List
reply *rule.List
next plugin.Handler
}
//New build a new firewall plugin
func New() (*firewall, error) {
pol := &firewall{engines: map[string]policy.Engine{"--default--": policy.NewExprEngine()}}
var err error
if pol.query, err = rule.NewList(policy.TypeBlock, false); err != nil {
return nil, err
}
if pol.reply, err = rule.NewList(policy.TypeAllow, true); err != nil {
return nil, err
}
return pol, nil
}
// ServeDNS implements the Handler interface.
func (p *firewall) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) {
var (
status = -1
respMsg *dns.Msg
errfw error
queryData = make(map[string]interface{}, 0)
)
state := request.Request{W: w, Req: r}
// evaluate query to determine action
action, err := p.query.Evaluate(ctx, state, queryData, p.engines)
if err != nil {
m := new(dns.Msg)
m = m.SetRcode(r, dns.RcodeServerFailure)
w.WriteMsg(m)
return dns.RcodeSuccess, err
}
if action == policy.TypeAllow {
// if Allow : ask next plugin to resolve the DNS query
// temp writer: hold the DNS response until evaluation of the Reply Rulelist
writer := nonwriter.New(w)
// RequestDataExtractor requires a response.Reader to be able to evaluate the information on the DNS response
reader := response.NewReader(writer)
// ask other plugins to resolve
_, err := plugin.NextOrFailure(p.Name(), p.next, ctx, reader, r)
if err != nil {
m := new(dns.Msg)
m = m.SetRcode(r, dns.RcodeServerFailure)
w.WriteMsg(m)
return dns.RcodeSuccess, err
}
respMsg = writer.Msg
stateReply := request.Request{W: reader, Req: respMsg}
// whatever the response, send to the Reply RuleList for action
action, err = p.reply.Evaluate(ctx, stateReply, queryData, p.engines)
if err != nil {
m := new(dns.Msg)
m = m.SetRcode(r, dns.RcodeServerFailure)
w.WriteMsg(m)
return dns.RcodeSuccess, err
}
}
// Now apply the action evaluated by the RuleLists
switch action {
case policy.TypeAllow:
// the response from next plugin, whatever it is, is good to go
w.WriteMsg(respMsg)
return dns.RcodeSuccess, nil
case policy.TypeBlock:
// One of the RuleList ended evaluation with typeBlock : return the initial request with corresponding rcode
log.Debug("coredns::policy/firewall, Action is Block")
status = dns.RcodeNameError
case policy.TypeRefuse:
// One of the RuleList ended evaluation with typeRefuse : return the initial request with corresponding rcode
log.Debug("coredns::policy/firewall, Action is Refuse")
status = dns.RcodeRefused
case policy.TypeDrop:
// One of the RuleList ended evaluation with typeDrop : simulate a drop
log.Debug("coredns::policy/firewall, Action is Drop")
return dns.RcodeSuccess, nil
default:
// Any other action returned by RuleLists is considered an internal error
status = dns.RcodeServerFailure
errfw = errInvalidAction
}
m := new(dns.Msg)
m.SetRcode(r, status)
if errfw == nil {
w.WriteMsg(m)
}
return dns.RcodeSuccess, errfw
}
// Name implements the Handler interface.
func (p *firewall) Name() string { return "firewall" }