aboutsummaryrefslogtreecommitdiff
path: root/plugin/rewrite/README.md
blob: 4e2e49a3af3334778e6100fe1c89678614203b38 (plain) (blame)
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
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
# rewrite

## Name

*rewrite* - performs internal message rewriting.

## Description

Rewrites are invisible to the client. There are simple rewrites (fast) and complex rewrites
(slower), but they're powerful enough to accommodate most dynamic back-end applications.

## Syntax

A simplified/easy to digest syntax for *rewrite* is...
~~~
rewrite [continue|stop] FIELD FROM TO
~~~

* **FIELD** indicates what part of the request/response is being re-written.

   * `type` - the type field of the request will be rewritten. FROM/TO must be a DNS record type (`A`, `MX`, etc);
e.g., to rewrite ANY queries to HINFO, use `rewrite type ANY HINFO`.
   * `class` - the class of the message will be rewritten. FROM/TO must be a DNS class type (`IN`, `CH`, or `HS`) e.g., to rewrite CH queries to IN use `rewrite class CH IN`.
   * `name` - the query name in the _request_ is rewritten; by default this is a full match of the name, e.g., `rewrite name miek.nl example.org`. Other match types are supported, see the **Name Field Rewrites** section below.
   * `answer name` - the query name in the _response_ is rewritten.  This option has special restrictions and requirements, in particular it must always combined with a `name` rewrite.  See below in the **Response Rewrites** section.
   *  `edns0` - an EDNS0 option can be appended to the request as described below in the **EDNS0 Options** section.

* **FROM** is the name or type to match
* **TO** is the destination name or type to rewrite to

If you specify multiple rules and an incoming query matches on multiple rules, the rewrite
will behave as following
* `continue` will continue apply the next rule in the rule list.
* `stop` will consider the current rule is the last rule and will not continue.  Default behaviour
for not specifying this rule processing mode is `stop`

### Name Field Rewrites

The `rewrite` plugin offers the ability to match on the name in the question section of
a DNS request. The match could be exact, substring, or based on a prefix, suffix, or regular
expression.

The syntax for the name re-writing is as follows:

```
rewrite [continue|stop] name [exact|prefix|suffix|substring|regex] STRING STRING
```

The match type, i.e. `exact`, `substring`, etc., triggers re-write:

* **exact** (default): on exact match of the name in the question section of a request
* **substring**: on a partial match of the name in the question section of a request
* **prefix**: when the name begins with the matching string
* **suffix**: when the name ends with the matching string
* **regex**: when the name in the question section of a request matches a regular expression

If the match type is omitted, the `exact` match type is being assumed.

The following instruction allows re-writing the name in the query that
contains `service.us-west-1.example.org` substring.

```
rewrite name substring service.us-west-1.example.org service.us-west-1.consul
```

Thus:

* Incoming Request Name: `ftp.service.us-west-1.example.org`
* Re-written Request Name: `ftp.service.us-west-1.consul`

The following instruction uses regular expressions. The name in a request
matching `(.*)-(us-west-1)\.example\.org` regular expression is being replaces with
`{1}.service.{2}.consul`, where `{1}` and `{2}` are regular expression match groups.

```
rewrite name regex (.*)-(us-west-1)\.example\.org {1}.service.{2}.consul
```

Thus:

* Incoming Request Name: `ftp-us-west-1.example.org`
* Re-written Request Name: `ftp.service.us-west-1.consul`

### Response Rewrites

When re-writing incoming DNS requests' names, CoreDNS re-writes the `QUESTION SECTION`
section of the requests. It may be necessary to re-write the `ANSWER SECTION` of the
requests, because some DNS resolvers would treat the mismatch between `QUESTION SECTION`
and `ANSWER SECTION` as a man-in-the-middle attack (MITM).

For example, a user tries to resolve `ftp-us-west-1.coredns.rocks`. The
CoreDNS configuration file has the following rule:

```
rewrite name regex (.*)-(us-west-1)\.coredns\.rocks {1}.service.{2}.consul
```

CoreDNS instance re-wrote the request to `ftp-us-west-1.coredns.rocks` with
`ftp.service.us-west-1.consul` and ultimately resolved it to 3 records.
The resolved records, see `ANSWER SECTION`, were not from `coredns.rocks`, but
rather from `service.us-west-1.consul`.


```
$ dig @10.1.1.1 ftp-us-west-1.coredns.rocks

; <<>> DiG 9.8.3-P1 <<>> @10.1.1.1 ftp-us-west-1.coredns.rocks
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 8619
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 3, AUTHORITY: 0, ADDITIONAL: 0

;; QUESTION SECTION:
;ftp-us-west-1.coredns.rocks. IN A

;; ANSWER SECTION:
ftp.service.us-west-1.consul. 0    IN A    10.10.10.10
ftp.service.us-west-1.consul. 0    IN A    10.20.20.20
ftp.service.us-west-1.consul. 0    IN A    10.30.30.30
```

The above is the mismatch.

The following configuration snippet allows for the re-writing of the
`ANSWER SECTION`, provided that the `QUESTION SECTION` was re-written:

```
    rewrite stop {
        name regex (.*)-(us-west-1)\.coredns\.rocks {1}.service.{2}.consul
        answer name (.*)\.service\.(us-west-1)\.consul {1}-{2}.coredns.rocks
    }
```

Now, the `ANSWER SECTION` matches the `QUESTION SECTION`:

```
$ dig @10.1.1.1 ftp-us-west-1.coredns.rocks

; <<>> DiG 9.8.3-P1 <<>> @10.1.1.1 ftp-us-west-1.coredns.rocks
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 8619
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 3, AUTHORITY: 0, ADDITIONAL: 0

;; QUESTION SECTION:
;ftp-us-west-1.coredns.rocks. IN A

;; ANSWER SECTION:
ftp-us-west-1.coredns.rocks. 0    IN A    10.10.10.10
ftp-us-west-1.coredns.rocks. 0    IN A    10.20.20.20
ftp-us-west-1.coredns.rocks. 0    IN A    10.30.30.30
```

The syntax for the rewrite of DNS request and response is as follows:

```
rewrite [continue|stop] {
    name regex STRING STRING
    answer name STRING STRING
}
```

Note that the above syntax is strict.  For response rewrites only `name`
rules are allowed to match the question section, and only by match type
`regex`. The answer rewrite must be after the name, as ordered in the
syntax example. There must only be two lines (a `name` follwed by an
`answer`) in the brackets, additional rules are not supported.

An alternate syntax for the rewrite of DNS request and response is as
follows:

```
rewrite [continue|stop] name regex STRING STRING answer name STRING STRING
```

## EDNS0 Options

Using FIELD edns0, you can set, append, or replace specific EDNS0 options on the request.

* `replace` will modify any "matching" option with the specified option. The criteria for "matching" varies based on EDNS0 type.
* `append` will add the option only if no matching option exists
* `set` will modify a matching option or add one if none is found

Currently supported are `EDNS0_LOCAL`, `EDNS0_NSID` and `EDNS0_SUBNET`.

### EDNS0_LOCAL

This has two fields, code and data. A match is defined as having the same code. Data may be a string or a variable.

* A string data can be treated as hex if it starts with `0x`. Example:

~~~ corefile
. {
    rewrite edns0 local set 0xffee 0x61626364
    whoami
}
~~~

rewrites the first local option with code 0xffee, setting the data to "abcd". Equivalent:

~~~ corefile
. {
    rewrite edns0 local set 0xffee abcd
}
~~~

* A variable data is specified with a pair of curly brackets `{}`. Following are the supported variables by default:
  {qname}, {qtype}, {client_ip}, {client_port}, {protocol}, {server_ip}, {server_port}.
Any plugin that can provide it's own additional variables by implementing metadata.Provider interface. If you are going to use metadata variables then metadata plugin must be enabled.

Example:

~~~ corefile
. {
    metadata
    rewrite edns0 local set 0xffee {client_ip}
}
~~~

### EDNS0_NSID

This has no fields; it will add an NSID option with an empty string for the NSID. If the option already exists
and the action is `replace` or `set`, then the NSID in the option will be set to the empty string.

### EDNS0_SUBNET

This has two fields,  IPv4 bitmask length and IPv6 bitmask length. The bitmask
length is used to extract the client subnet from the source IP address in the query.

Example:

~~~
rewrite edns0 subnet set 24 56
~~~

* If the query has source IP as IPv4, the first 24 bits in the IP will be the network subnet.
* If the query has source IP as IPv6, the first 56 bits in the IP will be the network subnet.

## Full Syntax

The full plugin usage syntax is harder to digest...
~~~
rewrite [continue|stop] {type|class|edns0|name [exact|prefix|suffix|substring|regex [FROM TO answer name]]} FROM TO
~~~

The syntax above doesn't cover the multi line block option for specifying a name request+response rewrite rule described in the **Response Rewrite** section.