diff --git a/cmd/flags.go b/cmd/flags.go index 902d3526ea..4ec371e8cb 100644 --- a/cmd/flags.go +++ b/cmd/flags.go @@ -85,6 +85,10 @@ func CreateFlags(defaultPath string) []cli.Flag { Name: "http.memcached-host", Usage: "Set the memcached host(s) to use for HTTP based challenges. Challenges will be written to all specified hosts.", }, + &cli.StringFlag{ + Name: "http.nfqueueport", + Usage: "Set the port to use for HTTP based challange. but unlike http it will not bind that port and while other thing already binding that port.", + }, &cli.BoolFlag{ Name: "tls", Usage: "Use the TLS challenge to solve challenges. Can be mixed with other types of challenges.", diff --git a/cmd/setup_challenges.go b/cmd/setup_challenges.go index 938ee74592..469a062ad8 100644 --- a/cmd/setup_challenges.go +++ b/cmd/setup_challenges.go @@ -13,6 +13,7 @@ import ( "github.com/go-acme/lego/v4/log" "github.com/go-acme/lego/v4/providers/dns" "github.com/go-acme/lego/v4/providers/http/memcached" + nfqueue "github.com/go-acme/lego/v4/providers/http/nfqueue" "github.com/go-acme/lego/v4/providers/http/webroot" "github.com/urfave/cli/v2" ) @@ -55,6 +56,12 @@ func setupHTTPProvider(ctx *cli.Context) challenge.Provider { log.Fatal(err) } return ps + case ctx.IsSet("http.nfqueueport"): + ps, err := nfqueue.NewHttpDpiProvider(ctx.String("http.nfqueueport")) + if err != nil { + log.Fatal(err) + } + return ps case ctx.IsSet("http.port"): iface := ctx.String("http.port") if !strings.Contains(iface, ":") { diff --git a/go.mod b/go.mod index cb2425ee98..bf66b9077c 100644 --- a/go.mod +++ b/go.mod @@ -71,6 +71,11 @@ require ( software.sslmate.com/src/go-pkcs12 v0.2.0 ) +require ( + github.com/florianl/go-nfqueue v1.3.1 + github.com/google/gopacket v1.1.19 +) + require ( github.com/Azure/go-autorest v14.2.0+incompatible // indirect github.com/Azure/go-autorest/autorest/adal v0.9.18 // indirect @@ -92,18 +97,22 @@ require ( github.com/golang-jwt/jwt/v4 v4.2.0 // indirect github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect github.com/golang/protobuf v1.5.2 // indirect + github.com/google/go-cmp v0.5.8 // indirect github.com/google/uuid v1.3.0 // indirect github.com/googleapis/gax-go/v2 v2.0.5 // indirect github.com/hashicorp/errwrap v1.0.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect + github.com/josharian/native v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213 // indirect github.com/kolo/xmlrpc v0.0.0-20200310150728-e0350524596b // indirect github.com/labbsr0x/goh v1.0.1 // indirect github.com/liquidweb/go-lwApi v0.0.5 // indirect github.com/liquidweb/liquidweb-cli v0.6.9 // indirect + github.com/mdlayher/netlink v1.6.0 // indirect + github.com/mdlayher/socket v0.1.1 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect @@ -123,6 +132,7 @@ require ( go.opencensus.io v0.22.3 // indirect go.uber.org/ratelimit v0.2.0 // indirect golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect + golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 // indirect golang.org/x/sys v0.4.0 // indirect golang.org/x/text v0.6.0 // indirect golang.org/x/tools v0.1.12 // indirect diff --git a/go.sum b/go.sum index 293d747509..5cf94f685a 100644 --- a/go.sum +++ b/go.sum @@ -132,6 +132,8 @@ github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5Kwzbycv github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= +github.com/florianl/go-nfqueue v1.3.1 h1:khQ9fYCrjbu5CF8dZF55G2RTIEIQRI0Aj5k3msJR6Gw= +github.com/florianl/go-nfqueue v1.3.1/go.mod h1:aHWbgkhryJxF5XxYvJ3oRZpdD4JP74Zu/hP1zuhja+M= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU= @@ -215,13 +217,17 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= +github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-github/v32 v32.1.0/go.mod h1:rIEpZD9CTDQwDK9GDrtMTycQNA4JU3qBsCizh3q2WCI= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8= +github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= @@ -300,6 +306,8 @@ github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHW github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/josharian/native v1.0.0 h1:Ts/E8zCSEsG17dUqv7joXJFybuMLjQfWE04tsBODTxk= +github.com/josharian/native v1.0.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= @@ -373,6 +381,10 @@ github.com/mattn/go-runewidth v0.0.6/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-tty v0.0.3/go.mod h1:ihxohKRERHTVzN+aSVRwACLCeqIoZAWpoICkkvrWyR0= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/mdlayher/netlink v1.6.0 h1:rOHX5yl7qnlpiVkFWoqccueppMtXzeziFjWAjLg6sz0= +github.com/mdlayher/netlink v1.6.0/go.mod h1:0o3PlBmGst1xve7wQ7j/hwpNaFaH4qCRyWCdcZk8/vA= +github.com/mdlayher/socket v0.1.1 h1:q3uOGirUPfAV2MUoaC7BavjQ154J7+JOkTWyiV+intI= +github.com/mdlayher/socket v0.1.1/go.mod h1:mYV5YIZAfHh4dzDVzI8x8tWLWCliuX8Mon5Awbj+qDs= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/dns v1.1.47/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME= github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA= @@ -675,7 +687,9 @@ golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96b golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210913180222-943fd674d43e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210928044308-7d9f5e0b762b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.5.0 h1:GyT4nK/YDHSqa1c4753ouYCDajOYKTja9Xb/OHtgvSw= golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= @@ -696,6 +710,7 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 h1:uVc8UZUe6tr40fFVnUP5Oj+veunVezqYl9z7DYw9xzw= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -745,6 +760,7 @@ golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/providers/http/nfqueue/nfqueue.go b/providers/http/nfqueue/nfqueue.go new file mode 100644 index 0000000000..4456170686 --- /dev/null +++ b/providers/http/nfqueue/nfqueue.go @@ -0,0 +1,196 @@ +// Package nfqueue implements a HTTP provider for solving the HTTP-01 challenge using nfqueue +// by captureing http challange pacet in fly and answering it by ourself +package nfqueue + +import ( + "bufio" + "bytes" + "context" + "fmt" + "log" + "net" + "net/http" + "os/exec" + "runtime" + "strings" + "time" + + gnfqueue "github.com/florianl/go-nfqueue" + "github.com/google/gopacket" + "github.com/google/gopacket/layers" +) + +// HTTPProvider implements HTTPProvider for `http-01` challenge. +type HTTPProvider struct { + port string + context context.Context + cancel context.CancelFunc +} + +// NewHttpDpiProvider returns a HTTPProvider instance with a configured port. +func NewHttpDpiProvider(port string) (*HTTPProvider, error) { + + c := &HTTPProvider{ + port: port, + } + + return c, nil +} + +// this craft acme challange response in HTTP level +func craftkeyauthresponse(keyAuth string) []byte { + var reply []byte + reply = fmt.Append(reply, "HTTP/1.1 200 OK\r\n") + reply = fmt.Append(reply, "Content-Type: text/plain\r\n") + reply = fmt.Append(reply, "server: go-acme-nfqueue\r\n") + reply = fmt.Appendf(reply, "Content-Length: %d\r\n", len(keyAuth)) + reply = fmt.Append(reply, "\r\n", keyAuth) + + return reply +} + +// craft packet +func craftReplyPacketBytes(keyAuth string, inputpacket gopacket.Packet) []byte { + outbuffer := gopacket.NewSerializeBuffer() + opt := gopacket.SerializeOptions{ + FixLengths: true, + ComputeChecksums: true, + } + inputTcp := inputpacket.Layer(layers.LayerTypeTCP).(*layers.TCP) + inputIPv4 := inputpacket.Layer(layers.LayerTypeIPv4).(*layers.IPv4) + + httplayer := gopacket.Payload(craftkeyauthresponse(keyAuth)) + tcplayer := &layers.TCP{ + // we reply back so reverse src and dst ports + SrcPort: inputTcp.DstPort, + DstPort: inputTcp.SrcPort, + Ack: inputTcp.Seq + uint32(len(inputTcp.Payload)), + Seq: inputTcp.Ack, + PSH: true, + ACK: true, + } + // log.Infof("dstp: %s, srcp %s", tcplayer.DstPort.String(), tcp) + //check network layer + // this is reply so we reverse sorce and dst ip + iplayer := &layers.IPv4{ + SrcIP: inputIPv4.DstIP, + DstIP: inputIPv4.SrcIP, + } + tcplayer.SetNetworkLayerForChecksum(iplayer) + gopacket.SerializeLayers(outbuffer, opt, tcplayer, httplayer) + + return outbuffer.Bytes() +} + +// sendPacket sends packet: TODO: call cleanup if errors out +func sendPacket(packet []byte, DstIP *net.IP) error { + var err error + con, err := net.Dial("ip:6", DstIP.String()) + if err != nil { + return err + } + _, err = con.Write(packet) + if err != nil { + return err + } + return nil +} + +// serve runs server by sniffing packets on firewall and inject response into it. +// iptables :// +func (w *HTTPProvider) serve(domain, token, keyAuth string) error { + //run nfqueue start + cmd := exec.Command("iptables", "-I", "INPUT", "-p", "tcp", "--dport", w.port, "-j", "NFQUEUE", "--queue-num", "8555") + err := cmd.Run() + // ensure even if clean funtion failed to called + defer exec.Command("iptables", "-D", "INPUT", "-p", "tcp", "--dport", w.port, "-j", "NFQUEUE", "--queue-num", "8555").Run() + if err != nil { + return err + } + config := gnfqueue.Config{ + NfQueue: 8555, + MaxPacketLen: 0xFFFF, + MaxQueueLen: 0xFF, + Copymode: gnfqueue.NfQnlCopyPacket, + WriteTimeout: 15 * time.Millisecond, + } + nf, err := gnfqueue.Open(&config) + if err != nil { + return err + } + defer nf.Close() + + //handle Packet + handlepacket := func(a gnfqueue.Attribute) int { + id := *a.PacketID + opt := gopacket.DecodeOptions{ + NoCopy: true, + Lazy: false, + } + //assume ipv4 for now, will segfault + payload := gopacket.NewPacket(*a.Payload, layers.LayerTypeIPv4, opt) + ipL := payload.Layer(layers.LayerTypeIPv4) + srcip := ipL.(*layers.IPv4).SrcIP + if tcpLayer := payload.Layer(layers.LayerTypeTCP); tcpLayer != nil { + // Get actual TCP data from this layer + inputTcp, _ := tcpLayer.(*layers.TCP) + // this should be HTTP payload + httpPayload, err := http.ReadRequest(bufio.NewReader((bytes.NewReader(inputTcp.LayerPayload())))) + if err != nil { + nf.SetVerdict(id, gnfqueue.NfAccept) + return 0 + } + // check token in http + if strings.Contains(httpPayload.URL.Path, token) { + //we got the token!, block the packet to backend server. + nf.SetVerdict(id, gnfqueue.NfDrop) + //forge our new reply + replypacket := craftReplyPacketBytes(keyAuth, payload) + // Send the modified packet back to VA, ignore err as it won't crash + sendPacket(replypacket, &srcip) + // packet sent, end of function + return 0 + } else { + nf.SetVerdict(id, gnfqueue.NfAccept) + return 0 + } + + } else { + nf.SetVerdict(id, gnfqueue.NfAccept) + } + + return 0 + } + + // Register your function to listen on nflqueue queue + err = nf.Register(w.context, handlepacket) + if err != nil { + fmt.Println(err) + return nil + } + + // Block till the context expires + <-w.context.Done() + return nil +} + +func (w *HTTPProvider) Present(domain, token, keyAuth string) error { + // test if OS is linux, otherwise no point running this nfqueue is linux thing + if runtime.GOOS != "linux" { + log.Panicf("[%s] http-nfq provider isn't implimented non-linux", domain) + } + w.context, w.cancel = context.WithCancel(context.Background()) + go w.serve(domain, token, keyAuth) + return nil +} + +// CleanUp removes the firewall rule created for the challenge. +// solve should removed it already but just do be safe: +// iptables -D INPUT -p tcp --dport Port -j NFQUEUE --queue-num 8555 +func (w *HTTPProvider) CleanUp(domain, token, keyAuth string) error { + cmd := exec.Command("iptables", "-D", "INPUT", "-p", "tcp", "--dport", w.port, "-j", "NFQUEUE", "--queue-num", "8555") + cmd.Run() + // tell nfqueue to shut down + w.cancel() + return nil +}