mirror of
https://github.com/coredns/coredns.git
synced 2025-10-27 08:14:18 -04:00
[rewrite] Introduce cname target rewrite rule to rewrite plugin (#6004)
* cname target rewrite part in answer sec tion Signed-off-by: amila <amila.15@cse.mrt.ac.lk> * upstream request Signed-off-by: amila <amila.15@cse.mrt.ac.lk> * fix looping issue Signed-off-by: amila <amila.15@cse.mrt.ac.lk> * support exact, prefix, suffix, substring, and regex types for cname rewrite Signed-off-by: amila <amila.15@cse.mrt.ac.lk> * support any qtype, corrected prefix, suffix, substring types behavior Signed-off-by: amila <amila.15@cse.mrt.ac.lk> * unit tests added, mocked the upstream call Signed-off-by: amila <amila.15@cse.mrt.ac.lk> * fix lint errors Signed-off-by: amila <amila.15@cse.mrt.ac.lk> * add newline to fix test issue Signed-off-by: amila <amila.15@cse.mrt.ac.lk> * add default rewrite type, add readme Signed-off-by: amila <amila.15@cse.mrt.ac.lk> * readme grammar fix Signed-off-by: amila <amila.15@cse.mrt.ac.lk> * reuse rewrite types Signed-off-by: amila <amila.15@cse.mrt.ac.lk> * comment fixed Signed-off-by: amila <amila.15@cse.mrt.ac.lk> --------- Signed-off-by: amila <amila.15@cse.mrt.ac.lk>
This commit is contained in:
145
plugin/rewrite/cname_target.go
Normal file
145
plugin/rewrite/cname_target.go
Normal file
@@ -0,0 +1,145 @@
|
||||
package rewrite
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/coredns/coredns/plugin/pkg/log"
|
||||
"github.com/coredns/coredns/plugin/pkg/upstream"
|
||||
"github.com/coredns/coredns/request"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
// UpstreamInt wraps the Upstream API for dependency injection during testing
|
||||
type UpstreamInt interface {
|
||||
Lookup(ctx context.Context, state request.Request, name string, typ uint16) (*dns.Msg, error)
|
||||
}
|
||||
|
||||
// cnameTargetRule is cname target rewrite rule.
|
||||
type cnameTargetRule struct {
|
||||
rewriteType string
|
||||
paramFromTarget string
|
||||
paramToTarget string
|
||||
nextAction string
|
||||
state request.Request
|
||||
ctx context.Context
|
||||
Upstream UpstreamInt // Upstream for looking up external names during the resolution process.
|
||||
}
|
||||
|
||||
func (r *cnameTargetRule) getFromAndToTarget(inputCName string) (from string, to string) {
|
||||
switch r.rewriteType {
|
||||
case ExactMatch:
|
||||
return r.paramFromTarget, r.paramToTarget
|
||||
case PrefixMatch:
|
||||
if strings.HasPrefix(inputCName, r.paramFromTarget) {
|
||||
return inputCName, r.paramToTarget + strings.TrimPrefix(inputCName, r.paramFromTarget)
|
||||
}
|
||||
case SuffixMatch:
|
||||
if strings.HasSuffix(inputCName, r.paramFromTarget) {
|
||||
return inputCName, strings.TrimSuffix(inputCName, r.paramFromTarget) + r.paramToTarget
|
||||
}
|
||||
case SubstringMatch:
|
||||
if strings.Contains(inputCName, r.paramFromTarget) {
|
||||
return inputCName, strings.Replace(inputCName, r.paramFromTarget, r.paramToTarget, -1)
|
||||
}
|
||||
case RegexMatch:
|
||||
pattern := regexp.MustCompile(r.paramFromTarget)
|
||||
regexGroups := pattern.FindStringSubmatch(inputCName)
|
||||
if len(regexGroups) == 0 {
|
||||
return "", ""
|
||||
}
|
||||
substitution := r.paramToTarget
|
||||
for groupIndex, groupValue := range regexGroups {
|
||||
groupIndexStr := "{" + strconv.Itoa(groupIndex) + "}"
|
||||
substitution = strings.Replace(substitution, groupIndexStr, groupValue, -1)
|
||||
}
|
||||
return inputCName, substitution
|
||||
}
|
||||
return "", ""
|
||||
}
|
||||
|
||||
func (r *cnameTargetRule) RewriteResponse(res *dns.Msg, rr dns.RR) {
|
||||
// logic to rewrite the cname target of dns response
|
||||
switch rr.Header().Rrtype {
|
||||
case dns.TypeCNAME:
|
||||
// rename the target of the cname response
|
||||
if cname, ok := rr.(*dns.CNAME); ok {
|
||||
fromTarget, toTarget := r.getFromAndToTarget(cname.Target)
|
||||
if cname.Target == fromTarget {
|
||||
// create upstream request with the new target with the same qtype
|
||||
r.state.Req.Question[0].Name = toTarget
|
||||
upRes, err := r.Upstream.Lookup(r.ctx, r.state, toTarget, r.state.Req.Question[0].Qtype)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf("Error upstream request %v", err)
|
||||
}
|
||||
|
||||
var newAnswer []dns.RR
|
||||
// iterate over first upstram response
|
||||
// add the cname record to the new answer
|
||||
for _, rr := range res.Answer {
|
||||
if cname, ok := rr.(*dns.CNAME); ok {
|
||||
// change the target name in the response
|
||||
cname.Target = toTarget
|
||||
newAnswer = append(newAnswer, rr)
|
||||
}
|
||||
}
|
||||
// iterate over upstream response recieved
|
||||
for _, rr := range upRes.Answer {
|
||||
if rr.Header().Name == toTarget {
|
||||
newAnswer = append(newAnswer, rr)
|
||||
}
|
||||
}
|
||||
res.Answer = newAnswer
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func newCNAMERule(nextAction string, args ...string) (Rule, error) {
|
||||
var rewriteType string
|
||||
var paramFromTarget, paramToTarget string
|
||||
if len(args) == 3 {
|
||||
rewriteType = (strings.ToLower(args[0]))
|
||||
switch rewriteType {
|
||||
case ExactMatch:
|
||||
case PrefixMatch:
|
||||
case SuffixMatch:
|
||||
case SubstringMatch:
|
||||
case RegexMatch:
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown cname rewrite type: %s", rewriteType)
|
||||
}
|
||||
paramFromTarget, paramToTarget = strings.ToLower(args[1]), strings.ToLower(args[2])
|
||||
} else if len(args) == 2 {
|
||||
rewriteType = ExactMatch
|
||||
paramFromTarget, paramToTarget = strings.ToLower(args[0]), strings.ToLower(args[1])
|
||||
} else {
|
||||
return nil, fmt.Errorf("too few (%d) arguments for a cname rule", len(args))
|
||||
}
|
||||
rule := cnameTargetRule{
|
||||
rewriteType: rewriteType,
|
||||
paramFromTarget: paramFromTarget,
|
||||
paramToTarget: paramToTarget,
|
||||
nextAction: nextAction,
|
||||
Upstream: upstream.New(),
|
||||
}
|
||||
return &rule, nil
|
||||
}
|
||||
|
||||
// Rewrite rewrites the current request.
|
||||
func (r *cnameTargetRule) Rewrite(ctx context.Context, state request.Request) (ResponseRules, Result) {
|
||||
if len(r.rewriteType) > 0 && len(r.paramFromTarget) > 0 && len(r.paramToTarget) > 0 {
|
||||
r.state = state
|
||||
r.ctx = ctx
|
||||
return ResponseRules{r}, RewriteDone
|
||||
}
|
||||
return nil, RewriteIgnored
|
||||
}
|
||||
|
||||
// Mode returns the processing mode.
|
||||
func (r *cnameTargetRule) Mode() string { return r.nextAction }
|
||||
Reference in New Issue
Block a user