func writeAccessLog(handle http.Handler, accessLogHandle *os.File) http.HandlerFunc {
	logger := golog.New(accessLogHandle, "", 0)
	return func(w http.ResponseWriter, request *http.Request) {
		writer := statusWriter{w, 0, 0}
		handle.ServeHTTP(&writer, request)
		end := time.Now()
		statusCode := writer.status
		length := writer.length
		if request.URL.RawQuery != "" {
			logger.Printf("%v %s %s \"%s %s%s%s %s\" %d %d \"%s\"", end.Format("2006/01/02 15:04:05"), request.Host, realip.FromRequest(request), request.Method, request.URL.Path, "?", request.URL.RawQuery, request.Proto, statusCode, length, request.Header.Get("User-Agent"))
		} else {
			logger.Printf("%v %s %s \"%s %s %s\" %d %d \"%s\"", end.Format("2006/01/02 15:04:05"), request.Host, realip.FromRequest(request), request.Method, request.URL.Path, request.Proto, statusCode, length, request.Header.Get("User-Agent"))
		}
	}
}
writeAccessLog Logs the Http Status for a request into fileHandler and returns a httphandler function which is a wrapper to log the requests.
package acmeproxy
import (
	"encoding/json"
	golog "log"
	"net/http"
	"os"
	"strings"
	"time"
	auth "github.com/abbot/go-http-auth"
	log "github.com/sirupsen/logrus"
	"github.com/go-acme/lego/v3/challenge"
	"github.com/go-acme/lego/v3/challenge/dns01"
	"golang.org/x/net/context"
	"github.com/orange-cloudfoundry/ipfiltering"
	"github.com/codeskyblue/realip"
)
const (
	ModeDefault   string = "default"
	ModeRaw       string = "raw"
	ActionPresent string = "present"
	ActionCleanup string = "cleanup"
)
type providerSolved interface {
	challenge.Provider
	CreateRecord(fqdn, value string) error
	RemoveRecord(fqdn, value string) error
}
type messageDefault struct {
	FQDN  string `json:"fqdn"`
	Value string `json:"value"`
}
type messageRaw struct {
	Domain  string `json:"domain"`
	Token   string `json:"token"`
	KeyAuth string `json:"keyauth"`
}
type messageIncoming struct {
	messageDefault
	messageRaw
}
type statusWriter struct {
	http.ResponseWriter
	status int
	length int
}
type AuthenticatorInterface interface {
	NewContext(ctx context.Context, r *http.Request) context.Context
}
func GetHandler(config *Config) http.Handler {
	mux := http.NewServeMux()
	handlerPresent := ActionHandler(ActionPresent, config)
	handlerCleanup := ActionHandler(ActionCleanup, config)
	if len(config.HtpasswdFile) > 0 {
		authenticator := &auth.BasicAuth{
			Realm:   "Basic Realm",
			Secrets: auth.HtpasswdFileProvider(config.HtpasswdFile),
		}
		handlerPresent = AuthenticationHandler(handlerPresent, ActionPresent, authenticator)
		handlerCleanup = AuthenticationHandler(handlerCleanup, ActionCleanup, authenticator)
	}
	if len(config.AllowedIPs) > 0 {
		handlerPresent = FilterHandler(handlerPresent, ActionPresent, config)
		handlerCleanup = FilterHandler(handlerCleanup, ActionCleanup, config)
	}
	mux.Handle("/", HomeHandler())
	mux.Handle("/present", handlerPresent)
	mux.Handle("/cleanup", handlerCleanup)
	var handler http.Handler
	if len(config.AccesslogFile) > 0 {
		accessLogHandle, err := os.OpenFile(config.AccesslogFile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
		if err != nil {
			panic(err)
		}
		defer accessLogHandle.Close()
		handler = writeAccessLog(mux, accessLogHandle)
	} else {
		handler = mux
	}
	return handler
}
func HomeHandler() http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		http.Error(w, "", http.StatusForbidden)
		log.Warning("Trying to access non-acmeproxy URL")
	})
}
func ActionHandler(action string, config *Config) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		alog := log.WithFields(log.Fields{
			"prefix": action + ": " + realip.FromRequest(r),
		})
		if r.Method != http.MethodPost {
			http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
			alog.WithField("method", r.Method).Error("Method not allowed")
			return
		}
		incoming := &messageIncoming{}
		err := json.NewDecoder(r.Body).Decode(incoming)
		if err != nil {
			http.Error(w, "Bad JSON request", http.StatusBadRequest)
			alog.WithField("error", err.Error()).Error("Method not allowed")
			return
		}
		incoming.FQDN = dns01.ToFqdn(incoming.FQDN)
		incoming.Domain = dns01.UnFqdn(incoming.Domain)
		var mode string
		var checkDomain string
		if incoming.FQDN != "" && incoming.Value != "" {
			mode = ModeDefault
			checkDomain = dns01.UnFqdn(strings.TrimPrefix(incoming.FQDN, "_acme-challenge."))
			alog.WithFields(log.Fields{
				"fqdn":  incoming.FQDN,
				"value": incoming.Value,
			}).Debug("Received JSON payload (default mode)")
		} else if incoming.Domain != "" && (incoming.Token != "" || incoming.KeyAuth != "") {
			mode = ModeRaw
			checkDomain = incoming.Domain
			alog.WithFields(log.Fields{
				"domain":  incoming.Domain,
				"token":   incoming.Token,
				"keyAuth": incoming.KeyAuth,
			}).Debug("Received JSON payload (raw mode)")
		} else {
			http.Error(w, "Wrong JSON content", http.StatusBadRequest)
			alog.WithField("json", incoming).Error("Wrong JSON content")
			return
		}
		var allowed = false
		for _, allowedDomain := range config.AllowedDomains {
			alog.WithFields(log.Fields{
				"checkDomain":   checkDomain,
				"allowedDomain": allowedDomain,
			}).Debug("Checking allowed domain")
			if checkDomain == allowedDomain || strings.HasSuffix(strings.SplitAfterN(checkDomain, ".", 2)[1], allowedDomain) {
				allowed = true
				break
			}
		}
		if !allowed {
			http.Error(w, "Requested domain not in allowed-domains", http.StatusInternalServerError)
			alog.WithFields(log.Fields{
				"domain":          checkDomain,
				"allowed-domains": config.AllowedDomains,
			}).Debug("Requested domain not in allowed-domains")
			return
		}
		if mode == ModeDefault {
			provider, ok := config.Provider.(providerSolved)
			if ok {
				alog.WithFields(log.Fields{
					"provider": config.ProviderName,
					"mode":     mode,
				}).Debug("Provider supports requested mode")
				if action == ActionPresent {
					err = provider.CreateRecord(incoming.FQDN, incoming.Value)
				} else if action == ActionCleanup {
					err = provider.RemoveRecord(incoming.FQDN, incoming.Value)
				} else {
					alog.WithFields(log.Fields{
						"provider": config.ProviderName,
						"fqdn":     incoming.FQDN,
						"value":    incoming.Value,
						"mode":     mode,
						"error":    err.Error(),
					}).Error("Wrong action specified")
					http.Error(w, "Wrong action specified", http.StatusInternalServerError)
					return
				}
				if err != nil {
					alog.WithFields(log.Fields{
						"provider": config.ProviderName,
						"fqdn":     incoming.FQDN,
						"value":    incoming.Value,
						"mode":     mode,
						"error":    err.Error(),
					}).Error("Failed to update TXT record")
					http.Error(w, "Failed to update TXT record", http.StatusInternalServerError)
					return
				}
			} else {
				http.Error(w, "Provider does not support requested mode", http.StatusInternalServerError)
				alog.WithFields(log.Fields{
					"provider": config.ProviderName,
					"mode":     mode,
				}).Debug("Provider does not support requested mode")
				return
			}
			m := messageDefault{FQDN: incoming.FQDN, Value: incoming.Value}
			w.Header().Set("Content-Type", "application/json")
			returnErr := json.NewEncoder(w).Encode(m)
			if returnErr != nil {
				log.Error("Problem encoding return message")
			}
			alog.WithFields(log.Fields{
				"provider": config.ProviderName,
				"fqdn":     incoming.FQDN,
				"value":    incoming.Value,
				"mode":     mode,
			}).Info("Sucessfully updated TXT record")
		} else if mode == ModeRaw {
			fqdn, value := dns01.GetRecord(incoming.Domain, incoming.KeyAuth)
			alog.WithFields(log.Fields{
				"provider": config.ProviderName,
				"mode":     mode,
			}).Debug("Provider supports requested mode")
			provider := config.Provider
			err = provider.Present(incoming.Domain, incoming.Token, incoming.KeyAuth)
			if err != nil {
				alog.WithFields(log.Fields{
					"provider": config.ProviderName,
					"domain":   incoming.Domain,
					"fqdn":     fqdn,
					"token":    incoming.Token,
					"keyAuth":  incoming.KeyAuth,
					"value":    value,
					"mode":     mode,
				}).Error("Failed to update TXT record")
				http.Error(w, "Failed to update TXT record", http.StatusInternalServerError)
				return
			}
			m := messageRaw{Domain: incoming.Domain, Token: incoming.Token, KeyAuth: incoming.KeyAuth}
			w.Header().Set("Content-Type", "application/json")
			returnErr := json.NewEncoder(w).Encode(m)
			if returnErr != nil {
				log.Error("Problem encoding return message")
			}
			alog.WithFields(log.Fields{
				"provider": config.ProviderName,
				"domain":   incoming.Domain,
				"fqdn":     fqdn,
				"token":    incoming.Token,
				"keyAuth":  incoming.KeyAuth,
				"value":    value,
				"mode":     mode,
			}).Info("Sucessfully updated TXT record")
		} else {
			http.Error(w, "Unkown mode requested", http.StatusInternalServerError)
			alog.WithFields(log.Fields{
				"provider": config.ProviderName,
				"mode":     mode,
			}).Info("Unknown mode requested")
			return
		}
	})
}
func AuthenticationHandler(h http.Handler, action string, a AuthenticatorInterface) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		ctx := a.NewContext(r.Context(), r)
		r = r.WithContext(ctx)
		authInfo := auth.FromContext(r.Context())
		authInfo.UpdateHeaders(w.Header())
		if authInfo == nil || !authInfo.Authenticated {
			http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
			log.Warning("Unauthorized request")
			return
		}
		log.WithField("username", authInfo.Username).Info("Authorized")
		h.ServeHTTP(w, r)
	})
}
func FilterHandler(h http.Handler, action string, config *Config) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		ip := realip.FromRequest(r)
		flog := log.WithFields(log.Fields{
			"prefix": action + ": " + ip,
			"ip": ip,
		})
		f := ipfiltering.New(ipfiltering.Options{AllowedIPs: config.AllowedIPs, BlockByDefault: true, Logger: flog})
		if !f.Allowed(ip) {
			http.Error(w, "Requesting IP not in allowed-ips", http.StatusForbidden)
			flog.Warning("Access denied")
			return
		}
		h.ServeHTTP(w, r)
	})
}