Refactor core

This commit is contained in:
Joona Hoikkala
2022-12-22 12:52:38 +02:00
parent 6ba9360156
commit 1405e6ab47
29 changed files with 1188 additions and 1479 deletions
+46
View File
@@ -0,0 +1,46 @@
package acmedns
import (
"github.com/google/uuid"
"net"
)
// Check if IP belongs to an allowed net
func (a ACMETxt) AllowedFrom(ip string) bool {
remoteIP := net.ParseIP(ip)
// Range not limited
if len(a.AllowFrom.ValidEntries()) == 0 {
return true
}
for _, v := range a.AllowFrom.ValidEntries() {
_, vnet, _ := net.ParseCIDR(v)
if vnet.Contains(remoteIP) {
return true
}
}
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 {
if len(ips) == 0 {
// If no IP provided, check if no whitelist present (everyone has access)
return a.AllowedFrom("")
}
for _, v := range ips {
if a.AllowedFrom(v) {
return true
}
}
return false
}
func NewACMETxt() ACMETxt {
var a = ACMETxt{}
password := generatePassword(40)
a.Username = uuid.New()
a.Password = password
a.Subdomain = uuid.New().String()
return a
}
+35
View File
@@ -0,0 +1,35 @@
package acmedns
import (
"encoding/json"
"net"
)
// cidrslice is a list of allowed cidr ranges
type Cidrslice []string
func (c *Cidrslice) JSON() string {
ret, _ := json.Marshal(c.ValidEntries())
return string(ret)
}
func (c *Cidrslice) IsValid() error {
for _, v := range *c {
_, _, err := net.ParseCIDR(sanitizeIPv6addr(v))
if err != nil {
return err
}
}
return nil
}
func (c *Cidrslice) ValidEntries() []string {
valid := []string{}
for _, v := range *c {
_, _, err := net.ParseCIDR(sanitizeIPv6addr(v))
if err == nil {
valid = append(valid, sanitizeIPv6addr(v))
}
}
return valid
}
+48
View File
@@ -0,0 +1,48 @@
package acmedns
import (
"errors"
"os"
"github.com/BurntSushi/toml"
)
func FileIsAccessible(fname string) bool {
_, err := os.Stat(fname)
if err != nil {
return false
}
f, err := os.Open(fname)
if err != nil {
return false
}
f.Close()
return true
}
func ReadConfig(fname string) (AcmeDnsConfig, error) {
var conf AcmeDnsConfig
_, err := toml.DecodeFile(fname, &conf)
if err != nil {
// Return with config file parsing errors from toml package
return conf, err
}
return prepareConfig(conf)
}
// prepareConfig checks that mandatory values exist, and can be used to set default values in the future
func prepareConfig(conf AcmeDnsConfig) (AcmeDnsConfig, error) {
if conf.Database.Engine == "" {
return conf, errors.New("missing database configuration option \"engine\"")
}
if conf.Database.Connection == "" {
return conf, errors.New("missing database configuration option \"connection\"")
}
// Default values for options added to config to keep backwards compatibility with old config
if conf.API.ACMECacheDir == "" {
conf.API.ACMECacheDir = "api-certs"
}
return conf, nil
}
+22
View File
@@ -0,0 +1,22 @@
package acmedns
import (
"database/sql"
"github.com/google/uuid"
)
type AcmednsDB interface {
Register(cidrslice Cidrslice) (ACMETxt, error)
GetByUsername(uuid.UUID) (ACMETxt, error)
GetTXTForDomain(string) ([]string, error)
Update(ACMETxtPost) error
GetBackend() *sql.DB
SetBackend(*sql.DB)
Close()
}
type AcmednsNS interface {
Start(errorChannel chan error)
SetOwnAuthKey(key string)
ParseRecords()
}
+72
View File
@@ -0,0 +1,72 @@
package acmedns
import "github.com/google/uuid"
type Account struct {
Username string
Password string
Subdomain string
}
// DNSConfig holds the config structure
type AcmeDnsConfig struct {
General general
Database dbsettings
API httpapi
Logconfig logconfig
}
// Config file general section
type general struct {
Listen string
Proto string `toml:"protocol"`
Domain string
Nsname string
Nsadmin string
Debug bool
StaticRecords []string `toml:"records"`
}
type dbsettings struct {
Engine string
Connection string
}
// API config
type httpapi struct {
Domain string `toml:"api_domain"`
IP string
DisableRegistration bool `toml:"disable_registration"`
AutocertPort string `toml:"autocert_port"`
Port string `toml:"port"`
TLS string
TLSCertPrivkey string `toml:"tls_cert_privkey"`
TLSCertFullchain string `toml:"tls_cert_fullchain"`
ACMECacheDir string `toml:"acme_cache_dir"`
NotificationEmail string `toml:"notification_email"`
CorsOrigins []string
UseHeader bool `toml:"use_header"`
HeaderName string `toml:"header_name"`
}
// Logging config
type logconfig struct {
Level string `toml:"loglevel"`
Logtype string `toml:"logtype"`
File string `toml:"logfile"`
Format string `toml:"logformat"`
}
// ACMETxt is the default structure for the user controlled record
type ACMETxt struct {
Username uuid.UUID
Password string
ACMETxtPost
AllowFrom Cidrslice
}
// ACMETxtPost holds the DNS part of the ACMETxt struct
type ACMETxtPost struct {
Subdomain string `json:"subdomain"`
Value string `json:"txt"`
}
+31
View File
@@ -0,0 +1,31 @@
package acmedns
import (
"crypto/rand"
"math/big"
"regexp"
)
func sanitizeIPv6addr(s string) string {
// Remove brackets from IPv6 addresses, net.ParseCIDR needs this
re, _ := regexp.Compile(`[\[\]]+`)
return re.ReplaceAllString(s, "")
}
func SanitizeString(s string) string {
// URL safe base64 alphabet without padding as defined in ACME
re, _ := regexp.Compile(`[^A-Za-z\-\_0-9]+`)
return re.ReplaceAllString(s, "")
}
func generatePassword(length int) string {
ret := make([]byte, length)
const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890-_"
alphalen := big.NewInt(int64(len(alphabet)))
for i := 0; i < length; i++ {
c, _ := rand.Int(rand.Reader, alphalen)
r := int(c.Int64())
ret[i] = alphabet[r]
}
return string(ret)
}