Added config option to check for a header value for clinet IP

This commit is contained in:
Joona Hoikkala 2016-12-02 17:04:16 +02:00
parent 8c99346b01
commit bf9eaf2f32
No known key found for this signature in database
GPG Key ID: C14AAE0F5ADCB854
8 changed files with 116 additions and 4 deletions

View File

@ -57,6 +57,17 @@ func (a ACMETxt) allowedFrom(ip string) bool {
return false
}
// Go through list (most likely from headers) to check for the IP.
// Reason for this is that some setups use reverse proxy in front of acme-dns
func (a ACMETxt) allowedFromList(ips []string) bool {
for _, v := range ips {
if a.allowedFrom(v) {
return true
}
}
return false
}
func newACMETxt() ACMETxt {
var a = ACMETxt{}
password := generatePassword(40)

12
api.go
View File

@ -9,6 +9,7 @@ import (
// Serve is an authentication middlware function used to authenticate update requests
func (a authMiddleware) Serve(ctx *iris.Context) {
allowUpdate := false
usernameStr := ctx.RequestHeader("X-Api-User")
password := ctx.RequestHeader("X-Api-Key")
postData := ACMETxt{}
@ -23,7 +24,16 @@ func (a authMiddleware) Serve(ctx *iris.Context) {
} else {
if correctPassword(password, au.Password) {
// Password ok
if au.allowedFrom(ctx.RequestIP()) {
// Now test for the possibly limited ranges
if DNSConf.API.UseHeader {
ips := getIPListFromHeader(ctx.RequestHeader(DNSConf.API.HeaderName))
allowUpdate = au.allowedFromList(ips)
} else {
allowUpdate = au.allowedFrom(ctx.RequestIP())
}
if allowUpdate {
// Update is allowed from remote addr
if err := ctx.ReadJSON(&postData); err == nil {
if au.Subdomain == postData.Subdomain {

View File

@ -19,6 +19,8 @@ func setupIris(t *testing.T, debug bool, noauth bool) *httpexpect.Expect {
Port: "8080",
TLS: "none",
CorsOrigins: []string{"*"},
UseHeader: false,
HeaderName: "X-Forwarded-For",
}
var dnscfg = DNSConfig{
API: httpapicfg,
@ -206,3 +208,48 @@ func TestApiManyUpdateWithCredentials(t *testing.T) {
Status(test.status)
}
}
func TestApiManyUpdateWithIpCheckHeaders(t *testing.T) {
updateJSON := map[string]interface{}{
"subdomain": "",
"txt": ""}
e := setupIris(t, false, false)
// Use header checks from default header (X-Forwarded-For)
DNSConf.API.UseHeader = true
// User without defined CIDR masks
newUser, err := DB.Register(cidrslice{})
if err != nil {
t.Errorf("Could not create new user, got error [%v]", err)
}
newUserWithCIDR, err := DB.Register(cidrslice{"192.168.1.2/32", "invalid"})
if err != nil {
t.Errorf("Could not create new user with CIDR, got error [%v]", err)
}
for _, test := range []struct {
user ACMETxt
headerValue string
status int
}{
{newUser, "whatever goes", 200},
{newUser, "10.0.0.1, 1.2.3.4 ,3.4.5.6", 200},
{newUserWithCIDR, "127.0.0.1", 401},
{newUserWithCIDR, "10.0.0.1, 10.0.0.2, 192.168.1.3", 401},
{newUserWithCIDR, "10.1.1.1 ,192.168.1.2, 8.8.8.8", 200},
} {
updateJSON = map[string]interface{}{
"subdomain": test.user.Subdomain,
"txt": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}
e.POST("/update").
WithJSON(updateJSON).
WithHeader("X-Api-User", test.user.Username.String()).
WithHeader("X-Api-Key", test.user.Password).
WithHeader("X-Forwarded-For", test.headerValue).
Expect().
Status(test.status)
}
DNSConf.API.UseHeader = false
}

View File

@ -44,6 +44,10 @@ tls_cert_fullchain = "/etc/tls/example.org/fullchain.pem"
corsorigins = [
"*"
]
# use HTTP header to get the client ip
use_header = false
# header name to pull the ip address / list of ip addresses from
header_name = "X-Forwarded-For"
[logconfig]
# logging level: "error", "warning", "info" or "debug"

View File

@ -68,6 +68,8 @@ func setupConfig() {
Port: "8080",
TLS: "none",
CorsOrigins: []string{"*"},
UseHeader: false,
HeaderName: "X-Forwarded-For",
}
var dnscfg = DNSConfig{

View File

@ -56,6 +56,8 @@ type httpapi struct {
TLSCertPrivkey string `toml:"tls_cert_privkey"`
TLSCertFullchain string `toml:"tls_cert_fullchain"`
CorsOrigins []string
UseHeader bool `toml:"use_header"`
HeaderName string `toml:"header_name"`
}
// Logging config

18
util.go
View File

@ -2,12 +2,13 @@ package main
import (
"crypto/rand"
"github.com/BurntSushi/toml"
log "github.com/Sirupsen/logrus"
"github.com/miekg/dns"
"math/big"
"regexp"
"strings"
"github.com/BurntSushi/toml"
log "github.com/Sirupsen/logrus"
"github.com/miekg/dns"
)
func readConfig(fname string) DNSConfig {
@ -68,3 +69,14 @@ func startDNS(listen string, proto string) *dns.Server {
go server.ListenAndServe()
return server
}
func getIPListFromHeader(header string) []string {
iplist := []string{}
for _, v := range strings.Split(header, ",") {
if len(v) > 0 {
// Ignore empty values
iplist = append(iplist, strings.TrimSpace(v))
}
}
return iplist
}

View File

@ -71,3 +71,27 @@ func TestReadConfig(t *testing.T) {
}
}
}
func TestGetIPListFromHeader(t *testing.T) {
for i, test := range []struct {
input string
output []string
}{
{"1.1.1.1, 2.2.2.2", []string{"1.1.1.1", "2.2.2.2"}},
{" 1.1.1.1 , 2.2.2.2", []string{"1.1.1.1", "2.2.2.2"}},
{",1.1.1.1 ,2.2.2.2", []string{"1.1.1.1", "2.2.2.2"}},
} {
res := getIPListFromHeader(test.input)
if len(res) != len(test.output) {
t.Errorf("Test %d: Expected [%d] items in return list, but got [%d]", i, len(test.output), len(res))
} else {
for j, vv := range test.output {
if res[j] != vv {
t.Errorf("Test %d: Expected return value [%v] but got [%v]", j, test.output, res)
}
}
}
}
}