From 3a65254902b110070238349c867878e1ffdd1ce0 Mon Sep 17 00:00:00 2001 From: m5r Date: Fri, 26 Jul 2024 00:56:24 +0200 Subject: [PATCH] env var working, got some validation done before running the cli --- Dockerfile | 15 +++---- cmd/root.go | 73 ++++++++++++++++++++-------------- compose.yml | 27 +++++++------ fly.toml | 9 ++--- go.mod | 5 ++- go.sum | 31 +++++++++------ http/server.go | 34 +++++++++++++--- xip/records.go | 105 +------------------------------------------------ xip/xip.go | 50 ++++++++++++++++++----- 9 files changed, 159 insertions(+), 190 deletions(-) diff --git a/Dockerfile b/Dockerfile index 994a62a..a319cab 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,25 +4,20 @@ WORKDIR /app COPY . . RUN go mod download -RUN go build -o /app/local-ip +RUN go build FROM gcr.io/distroless/base-debian12:latest WORKDIR /local-ip -COPY --from=build /app/local-ip /local-ip/local-ip +COPY --from=build /app/local-ip.sh /local-ip/local-ip.sh COPY --from=build /app/http/static /local-ip/http/static VOLUME /local-ip/.lego -# DNS -EXPOSE 53/udp -# HTTP -EXPOSE 80/tcp -# HTTPS -EXPOSE 443/tcp +# DNS HTTP HTTPS +EXPOSE 53/udp 80/tcp 443/tcp USER root -# TODO: make these configurable too -CMD ["/local-ip/local-ip", "--domain", "local-ip.sh", "--email", "admin@local-ip.sh", "--nameservers", "ns1.local-ip.sh.,ns2.local-ip.sh."] +CMD ["/local-ip/local-ip.sh"] diff --git a/cmd/root.go b/cmd/root.go index 8fe5b27..e9521fe 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -2,29 +2,48 @@ package cmd import ( "fmt" + "net/mail" "net/url" "strings" + "time" + "github.com/asaskevich/govalidator" "github.com/go-acme/lego/v4/lego" "github.com/spf13/cobra" "github.com/spf13/viper" + "local-ip.sh/certs" + "local-ip.sh/http" "local-ip.sh/utils" + "local-ip.sh/xip" ) var command = &cobra.Command{ Use: "local-ip.sh", PreRun: func(cmd *cobra.Command, args []string) { - nameservers, err := cmd.Flags().GetString("nameservers") - if err != nil { - utils.Logger.Fatal().Err(err).Msg("Unexpected error") - } - viper.Set("NameServers", strings.Split(nameservers, ",")) + viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_")) + viper.SetEnvPrefix("XIP") + viper.AutomaticEnv() - staging, err := cmd.Flags().GetBool("staging") + email := viper.GetString("Email") + _, err := mail.ParseAddress(email) if err != nil { - utils.Logger.Fatal().Err(err).Msg("Unexpected error") + utils.Logger.Fatal().Err(err).Msg("Invalid email address") } + domain := viper.GetString("Domain") + if !govalidator.IsDNSName(domain) { + utils.Logger.Fatal().Err(err).Msg("Invalid domain") + } + + nameservers := strings.Split(viper.GetString("nameservers"), ",") + for _, ns := range nameservers { + if !govalidator.IsIPv4(ns) { + utils.Logger.Fatal().Err(err).Str("ns", ns).Msg("Invalid name server") + } + } + viper.Set("NameServers", nameservers) + + staging := viper.GetBool("staging") var caDir string if staging { caDir = lego.LEDirectoryStaging @@ -35,36 +54,32 @@ var command = &cobra.Command{ parsedCaDirUrl, _ := url.Parse(caDir) caDirHostname := parsedCaDirUrl.Hostname() - email := viper.GetString("Email") - viper.Set("AccountFilePath", fmt.Sprintf("./.lego/accounts/%s/%s/account.json", caDirHostname, email)) viper.Set("KeyFilePath", fmt.Sprintf("./.lego/accounts/%s/%s/keys/%s.key", caDirHostname, email, email)) utils.InitConfig() }, Run: func(cmd *cobra.Command, args []string) { - c := utils.GetConfig() - fmt.Printf("%#v\n", c) - fmt.Println(c) - // n := xip.NewXip() + n := xip.NewXip() - // go func() { - // account := certs.LoadAccount() - // certsClient := certs.NewCertsClient(n, account) + go func() { + // try to obtain certificates once the DNS server is accepting requests + account := certs.LoadAccount() + certsClient := certs.NewCertsClient(n, account) - // time.Sleep(5 * time.Second) - // certsClient.RequestCertificates() + time.Sleep(5 * time.Second) + certsClient.RequestCertificates() - // for { - // // try to renew certificate every day - // time.Sleep(24 * time.Hour) - // certsClient.RequestCertificates() - // } - // }() + for { + // afterwards, try to renew certificates once a day + time.Sleep(24 * time.Hour) + certsClient.RequestCertificates() + } + }() - // go http.ServeHttp() + go http.ServeHttp() - // n.StartServer() + n.StartServer() }, } @@ -82,15 +97,13 @@ func Execute() { viper.BindPFlag("staging", command.Flags().Lookup("staging")) command.Flags().String("domain", "", "Root domain (required)") - command.MarkFlagRequired("domain") viper.BindPFlag("domain", command.Flags().Lookup("domain")) command.Flags().String("email", "", "ACME account email address (required)") - command.MarkFlagRequired("email") viper.BindPFlag("email", command.Flags().Lookup("email")) - command.Flags().String("nameservers", "", "List of nameservers separated by commas") - command.MarkFlagRequired("nameservers") + command.Flags().String("nameservers", "", "List of nameservers separated by commas (required)") + viper.BindPFlag("nameservers", command.Flags().Lookup("nameservers")) if err := command.Execute(); err != nil { utils.Logger.Fatal().Err(err).Msg("") diff --git a/compose.yml b/compose.yml index b06b38c..3cdd327 100644 --- a/compose.yml +++ b/compose.yml @@ -1,14 +1,19 @@ services: - local-ip.sh: - image: local-ip.sh - build: . - volumes: - - lego:/local-ip/.lego - restart: unless-stopped - ports: - - 53:53/udp - - 80:80/tcp - - 443:443/tcp + local-ip.sh: + image: local-ip.sh + build: . + volumes: + - lego:/local-ip/.lego + restart: unless-stopped + environment: + XIP_DOMAIN: "local-ip.sh" + XIP_EMAIL: "admin@local-ip.sh" + XIP_NAMESERVERS: "137.66.40.11,137.66.40.12" + # XIP_STAGING: true + ports: + - 53:53/udp + - 80:80/tcp + - 443:443/tcp volumes: - lego: + lego: diff --git a/fly.toml b/fly.toml index 5b5bd1d..8772e76 100644 --- a/fly.toml +++ b/fly.toml @@ -1,8 +1,3 @@ -# fly.toml app configuration file generated for local-ip-ancient-glade-4376 on 2023-11-29T11:43:10+01:00 -# -# See https://fly.io/docs/reference/configuration/ for information about how to use this file. -# - app = "local-ip" primary_region = "ams" kill_signal = "SIGINT" @@ -14,7 +9,9 @@ auto_rollback = true [build] [env] -PORT = "53" +XIP_DOMAIN = "local-ip.sh" +XIP_EMAIL = "admin@local-ip.sh" +XIP_NAMESERVERS = "137.66.40.11,137.66.40.12" # fly.io edge-only ip addresses, see https://community.fly.io/t/custom-domains-certificate-is-stuck-on-awaiting-configuration/8329 [mounts] source = "lego" diff --git a/go.mod b/go.mod index abb8468..5dd1872 100644 --- a/go.mod +++ b/go.mod @@ -3,9 +3,12 @@ module local-ip.sh go 1.22 require ( + github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 github.com/go-acme/lego/v4 v4.10.1 github.com/miekg/dns v1.1.57 github.com/rs/zerolog v1.33.0 + github.com/spf13/cobra v1.8.1 + github.com/spf13/viper v1.19.0 golang.org/x/net v0.23.0 gopkg.in/natefinch/lumberjack.v2 v2.2.1 ) @@ -26,9 +29,7 @@ require ( github.com/sourcegraph/conc v0.3.0 // indirect github.com/spf13/afero v1.11.0 // indirect github.com/spf13/cast v1.6.0 // indirect - github.com/spf13/cobra v1.8.1 // indirect github.com/spf13/pflag v1.0.5 // indirect - github.com/spf13/viper v1.19.0 // indirect github.com/subosito/gotenv v1.6.0 // indirect go.uber.org/atomic v1.9.0 // indirect go.uber.org/multierr v1.9.0 // indirect diff --git a/go.sum b/go.sum index e9ffe18..43c304b 100644 --- a/go.sum +++ b/go.sum @@ -1,11 +1,15 @@ +github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= +github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/cenkalti/backoff/v4 v4.2.0 h1:HN5dHm3WBOgndBH6E8V0q2jIYIR3s9yglV8k/+MN3u4= github.com/cenkalti/backoff/v4 v4.2.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/go-acme/lego/v4 v4.10.1 h1:MiJvoBXNdmAwEK/SImyhwZ8ZL4IR0jtWDD1wST+N138= @@ -13,12 +17,17 @@ github.com/go-acme/lego/v4 v4.10.1/go.mod h1:EMbf0Jmqwv94nJ5WL9qWnSXIBZnvsS9gNyp github.com/go-jose/go-jose/v3 v3.0.0 h1:s6rrhirfEP/CGIoc6p+PZAeogN2SxKav6Wp7+dyMWVo= github.com/go-jose/go-jose/v3 v3.0.0/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/google/go-cmp v0.5.0 h1:/QaMHBdZ26BB3SSst0Iwl10Epc+xhTquomWX0oZEB6w= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= @@ -34,9 +43,11 @@ github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RR github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8= github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= @@ -65,8 +76,6 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= @@ -78,8 +87,6 @@ go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= -golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= @@ -87,12 +94,10 @@ golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqR golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= -golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= -golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ= -golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -101,14 +106,14 @@ golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= -golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= diff --git a/http/server.go b/http/server.go index f69dcc3..826066d 100644 --- a/http/server.go +++ b/http/server.go @@ -2,8 +2,11 @@ package http import ( "context" + "fmt" + "net" "net/http" "os" + "strconv" "strings" "time" @@ -45,8 +48,9 @@ func registerHandlers() { } func serveHttp() *http.Server { - utils.Logger.Info().Msg("Starting up HTTP server on :80") - httpServer := &http.Server{Addr: ":http"} + config := utils.GetConfig() + httpServer := &http.Server{Addr: fmt.Sprintf(":%d", config.HttpPort)} + utils.Logger.Info().Str("http_address", httpServer.Addr).Msg("Starting up HTTP server") go func() { err := httpServer.ListenAndServe() if err != http.ErrServerClosed { @@ -84,22 +88,40 @@ func killServer(httpServer *http.Server) { } func redirectHttpToHttps() { - utils.Logger.Info().Msg("Redirecting HTTP traffic from :80 to HTTPS :443") + config := utils.GetConfig() httpServer := &http.Server{ - Addr: ":http", + Addr: fmt.Sprintf(":%d", config.HttpPort), Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { url := r.URL + host := r.Host + + // Strip the port from the host if present + if strings.Contains(host, ":") { + hostWithoutPort, _, err := net.SplitHostPort(host) + if err != nil { + utils.Logger.Error().Err(err).Msg("Failed to split host and port") + } else { + host = hostWithoutPort + } + } + // Add the HTTPS port only if it's not 443 + if config.HttpsPort != 443 { + host = net.JoinHostPort(host, strconv.FormatUint(uint64(config.HttpsPort), 10)) + } + url.Host = r.Host url.Scheme = "https" http.Redirect(w, r, url.String(), http.StatusMovedPermanently) }), } + utils.Logger.Info().Str("http_address", httpServer.Addr).Msg("Redirecting HTTP traffic to HTTPS") go httpServer.ListenAndServe() } func serveHttps() { - utils.Logger.Info().Msg("Starting up HTTPS server on :443") - httpsServer := &http.Server{Addr: ":https"} + config := utils.GetConfig() + httpsServer := &http.Server{Addr: fmt.Sprintf(":%d", config.HttpsPort)} + utils.Logger.Info().Str("https_address", httpsServer.Addr).Msg("Starting up HTTPS server") go httpsServer.ListenAndServeTLS("./.lego/certs/root/server.pem", "./.lego/certs/root/server.key") } diff --git a/xip/records.go b/xip/records.go index 6fe7207..13fce73 100644 --- a/xip/records.go +++ b/xip/records.go @@ -1,11 +1,9 @@ package xip import ( - "fmt" "net" "github.com/miekg/dns" - "local-ip.sh/utils" ) type hardcodedRecord struct { @@ -17,41 +15,8 @@ type hardcodedRecord struct { SRV *dns.SRV } -var config = utils.GetConfig() -var hardcodedRecords = map[string]hardcodedRecord{ - // TODO: maybe --nameservers ns1.local-ip.sh.=137.66.40.11,ns2.local-ip.sh.=137.66.40.12 - fmt.Sprintf("ns.%s.", config.Domain): { - // record holding ip addresses of ns1 and ns2 - A: []net.IP{ - net.IPv4(137, 66, 40, 11), - net.IPv4(137, 66, 40, 12), - }, - }, - fmt.Sprintf("ns1.%s.", config.Domain): { - A: []net.IP{ - net.IPv4(137, 66, 40, 11), // fly.io edge-only ip address, see https://community.fly.io/t/custom-domains-certificate-is-stuck-on-awaiting-configuration/8329 - }, - }, - fmt.Sprintf("ns2.%s.", config.Domain): { - A: []net.IP{ - net.IPv4(137, 66, 40, 12), // fly.io edge-only ip address #2 - }, - }, - fmt.Sprintf("%s.", config.Domain): { - // same as ns.local-ip.sh, it's the same machine :) - A: []net.IP{ - net.IPv4(137, 66, 40, 11), - net.IPv4(137, 66, 40, 12), - }, - }, - fmt.Sprintf("_acme-challenge.%s.", config.Domain): { - // will be filled in later when requesting the wildcard certificate - TXT: []string{}, - }, -} - -// additional records I set up to host emails, feel free to change or remove them for your own needs -var extraRecords = map[string]hardcodedRecord{ +var records = map[string]hardcodedRecord{ + // additional records I set up to host emails, feel free to change or remove them for your own needs "local-ip.sh.": { TXT: []string{"v=spf1 include:capsulecorp.dev ~all"}, MX: []*dns.MX{ @@ -85,69 +50,3 @@ var extraRecords = map[string]hardcodedRecord{ }, }, } -var records = mergeRecords(hardcodedRecords, extraRecords) - -func mergeRecords(a, b map[string]hardcodedRecord) map[string]hardcodedRecord { - result := make(map[string]hardcodedRecord) - for k, v := range a { - result[k] = v - } - for k, v := range b { - if r, ok := result[k]; ok { - result[k] = hardcodedRecord{ - A: uniqueIPs(append(r.A, v.A...)), - AAAA: uniqueIPs(append(r.AAAA, v.AAAA...)), - TXT: uniqueStrings(append(r.TXT, v.TXT...)), - MX: uniqueMX(append(r.MX, v.MX...)), - CNAME: uniqueStrings(append(r.CNAME, v.CNAME...)), - SRV: firstNonNil(r.SRV, v.SRV), - } - } else { - result[k] = v - } - } - return result -} - -func uniqueIPs(ips []net.IP) []net.IP { - seen := make(map[string]bool) - result := []net.IP{} - for _, ip := range ips { - if !seen[ip.String()] { - seen[ip.String()] = true - result = append(result, ip) - } - } - return result -} - -func uniqueStrings(strs []string) []string { - seen := make(map[string]bool) - result := []string{} - for _, str := range strs { - if !seen[str] { - seen[str] = true - result = append(result, str) - } - } - return result -} - -func uniqueMX(mxs []*dns.MX) []*dns.MX { - seen := make(map[string]uint16) - result := []*dns.MX{} - for _, mx := range mxs { - if pref, exists := seen[mx.Mx]; !exists || pref > mx.Preference { - seen[mx.Mx] = mx.Preference - result = append(result, mx) - } - } - return result -} - -func firstNonNil[T any](a, b *T) *T { - if a != nil { - return a - } - return b -} diff --git a/xip/xip.go b/xip/xip.go index 531ec64..87141f8 100644 --- a/xip/xip.go +++ b/xip/xip.go @@ -14,7 +14,7 @@ import ( type Xip struct { server dns.Server - nameServers []*dns.NS + nameServers []string } var ( @@ -25,6 +25,7 @@ var ( func (xip *Xip) SetTXTRecord(fqdn string, value string) { utils.Logger.Debug().Str("fqdn", fqdn).Str("value", value).Msg("Trying to set TXT record") + config := utils.GetConfig() if fqdn != fmt.Sprintf("_acme-challenge.%s.", config.Domain) { utils.Logger.Debug().Msg("Not allowed, abort") return @@ -38,6 +39,7 @@ func (xip *Xip) SetTXTRecord(fqdn string, value string) { func (xip *Xip) UnsetTXTRecord(fqdn string) { utils.Logger.Debug().Str("fqdn", fqdn).Msg("Trying to set TXT record") + config := utils.GetConfig() if fqdn != fmt.Sprintf("_acme-challenge.%s.", config.Domain) { utils.Logger.Debug().Msg("Not allowed, abort") return @@ -145,10 +147,10 @@ func (xip *Xip) handleNS(question dns.Question, message *dns.Msg) { Rrtype: dns.TypeNS, Class: dns.ClassINET, }, - Ns: ns.Ns, + Ns: ns, }) - additionals = append(additionals, xip.fqdnToA(ns.Ns)...) + additionals = append(additionals, xip.fqdnToA(ns)...) } for _, record := range nameServers { @@ -257,7 +259,15 @@ func (xip *Xip) handleSOA(question dns.Question, message *dns.Msg) { message.Answer = append(message.Answer, xip.soaRecord(question)) } +func emailToRname(email string) string { + parts := strings.SplitN(email, "@", 2) + localPart := strings.ReplaceAll(parts[0], ".", "\\.") + domain := parts[1] + return localPart + "." + domain + "." +} + func (xip *Xip) soaRecord(question dns.Question) *dns.SOA { + config := utils.GetConfig() soa := new(dns.SOA) soa.Hdr = dns.RR_Header{ Name: question.Name, @@ -266,8 +276,8 @@ func (xip *Xip) soaRecord(question dns.Question) *dns.SOA { Ttl: uint32((time.Minute * 5).Seconds()), Rdlength: 0, } - soa.Ns = xip.nameServers[0].Ns - soa.Mbox = config.Email + soa.Ns = xip.nameServers[0] + soa.Mbox = emailToRname(config.Email) soa.Serial = 2024072200 soa.Refresh = uint32((time.Minute * 15).Seconds()) soa.Retry = uint32((time.Minute * 15).Seconds()) @@ -321,6 +331,7 @@ func (xip *Xip) handleDnsRequest(response dns.ResponseWriter, request *dns.Msg) error := response.WriteMsg(message) if error != nil { + utils.Logger.Debug().Msg(message.String()) utils.Logger.Error().Err(error).Str("message", message.String()).Msg("Error responding to query") } }() @@ -335,6 +346,7 @@ func (xip *Xip) StartServer() { err := xip.server.ListenAndServe() defer xip.server.Shutdown() if err != nil { + utils.Logger.Fatal().Err(err).Msg("Failed to start DNS server") if strings.Contains(err.Error(), "fly-global-services: no such host") { // we're not running on fly, bind to 0.0.0.0 instead port := strings.Split(xip.server.Addr, ":")[1] @@ -349,16 +361,36 @@ func (xip *Xip) StartServer() { utils.Logger.Fatal().Err(err).Msg("Failed to start DNS server") } - utils.Logger.Info().Str("dns_address", xip.server.Addr).Msg("DNS server listening") + utils.Logger.Info().Str("dns_address", xip.server.Addr).Msg("Starting up DNS server") +} + +func (xip *Xip) initHardcodedRecords() { + config := utils.GetConfig() + rootDomainARecords := []net.IP{} + + for i, ns := range config.NameServers { + name := fmt.Sprintf("ns%d.%s.", i+1, config.Domain) + ip := net.ParseIP(ns) + + rootDomainARecords = append(rootDomainARecords, ip) + entry := records[name] + entry.A = append(records[name].A, ip) + records[name] = entry + + xip.nameServers = append(xip.nameServers, name) + } + + records[fmt.Sprintf("%s.", config.Domain)] = hardcodedRecord{A: rootDomainARecords} + + // will be filled in later when requesting the wildcard certificate + records[fmt.Sprintf("_acme-challenge.%s.", config.Domain)] = hardcodedRecord{TXT: []string{}} } func NewXip() (xip *Xip) { config := utils.GetConfig() xip = &Xip{} - for _, ns := range config.NameServers { - xip.nameServers = append(xip.nameServers, &dns.NS{Ns: ns}) - } + xip.initHardcodedRecords() xip.server = dns.Server{ Addr: fmt.Sprintf(":%d", config.DnsPort),