├── go.mod ├── .github └── workflows │ ├── golangci-lint.yml │ ├── golangci-lint-latest.yml │ ├── go.yml │ └── releases.yml ├── .golangci.yml ├── LICENSE ├── go.sum ├── save.go ├── save_v6.go ├── README.md ├── chain.go ├── chain_v6.go ├── main.go ├── rules_v6.go ├── rules.go ├── raw.go ├── raw_v6.go ├── nat.go └── nat_v6.go /go.mod: -------------------------------------------------------------------------------- 1 | module iptables-api 2 | 3 | go 1.12 4 | 5 | require ( 6 | github.com/abbot/go-http-auth v0.4.0 7 | github.com/gorilla/handlers v1.4.0 8 | github.com/gorilla/mux v1.7.2 9 | github.com/jeremmfr/go-iptables v0.4.3 10 | golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f // indirect 11 | golang.org/x/net v0.0.0-20190522155817-f3200d17e092 // indirect 12 | ) 13 | -------------------------------------------------------------------------------- /.github/workflows/golangci-lint.yml: -------------------------------------------------------------------------------- 1 | name: GolangCI-Lint 2 | on: [push, pull_request] 3 | jobs: 4 | golangci: 5 | name: lint 6 | runs-on: ubuntu-latest 7 | steps: 8 | - name: Check out code 9 | uses: actions/checkout@v2 10 | - name: golangci-lint 11 | uses: golangci/golangci-lint-action@v2 12 | with: 13 | version: v1.32 14 | args: -c .golangci.yml -v 15 | -------------------------------------------------------------------------------- /.github/workflows/golangci-lint-latest.yml: -------------------------------------------------------------------------------- 1 | name: GolangCI-Lint-Latest 2 | on: 3 | push: 4 | branches: 5 | - master 6 | jobs: 7 | run: 8 | name: Run 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Check out code 12 | uses: actions/checkout@v2 13 | - name: golangci-lint 14 | uses: golangci/golangci-lint-action@v2 15 | with: 16 | version: latest 17 | args: -c .golangci.yml -v 18 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | linters: 2 | enable-all: true 3 | disable: 4 | - funlen 5 | - wsl 6 | - dupl 7 | - gochecknoglobals 8 | - lll 9 | - godot 10 | - wrapcheck 11 | - nestif 12 | linters-settings: 13 | gocognit: 14 | # minimal code complexity to report, 30 by default 15 | min-complexity: 60 16 | issues: 17 | exclude-rules: 18 | - text: "G204: Subprocess launched with function call as argument or cmd arguments" 19 | linters: 20 | - gosec 21 | -------------------------------------------------------------------------------- /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Go Tests 2 | on: [push, pull_request] 3 | jobs: 4 | build: 5 | name: Build 6 | runs-on: ubuntu-latest 7 | steps: 8 | - name: Set up Go 1.13 9 | uses: actions/setup-go@v1 10 | with: 11 | go-version: 1.13 12 | id: go 13 | - name: Check out code 14 | uses: actions/checkout@v2 15 | - name: Get dependencies 16 | run: | 17 | go get -v -t -d ./... 18 | 19 | - name: Build 20 | run: go build -v . 21 | 22 | test: 23 | name: Test 24 | runs-on: ubuntu-latest 25 | steps: 26 | - name: Set up Go 1.13 27 | uses: actions/setup-go@v1 28 | with: 29 | go-version: 1.13 30 | id: go 31 | - name: Check out code 32 | uses: actions/checkout@v2 33 | - name: Get dependencies 34 | run: | 35 | go get -v -t -d ./... 36 | 37 | - name: Test 38 | run: go test -v ./... 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Jeremy Muriel 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/abbot/go-http-auth v0.4.0 h1:QjmvZ5gSC7jm3Zg54DqWE/T5m1t2AfDu6QlXJT0EVT0= 2 | github.com/abbot/go-http-auth v0.4.0/go.mod h1:Cz6ARTIzApMJDzh5bRMSUou6UMSp0IEXg9km/ci7TJM= 3 | github.com/gorilla/handlers v1.4.0 h1:XulKRWSQK5uChr4pEgSE4Tc/OcmnU9GJuSwdog/tZsA= 4 | github.com/gorilla/handlers v1.4.0/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ= 5 | github.com/gorilla/mux v1.7.2 h1:zoNxOV7WjqXptQOVngLmcSQgXmgk4NMz1HibBchjl/I= 6 | github.com/gorilla/mux v1.7.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= 7 | github.com/jeremmfr/go-iptables v0.4.3 h1:cEsNNkrOVNK7wXpgAElGMs0v2ZDptfmyyOLLgRHnrc0= 8 | github.com/jeremmfr/go-iptables v0.4.3/go.mod h1:+vww2OYD9R4qNFyMjQ04RgCv9M9hLuMGm91aPy+jYt4= 9 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 10 | golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f h1:R423Cnkcp5JABoeemiGEPlt9tHXFfw5kvc0yqlxRPWo= 11 | golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 12 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 13 | golang.org/x/net v0.0.0-20190522155817-f3200d17e092 h1:4QSRKanuywn15aTZvI/mIDEgPQpswuFndXpOj3rKEco= 14 | golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= 15 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 16 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 17 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 18 | -------------------------------------------------------------------------------- /.github/workflows/releases.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | on: 3 | push: 4 | tags: 5 | - "v*" 6 | jobs: 7 | release: 8 | name: Create Release 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Checkout code 12 | uses: actions/checkout@v2 13 | - name: Set env RELEASE_VERSION 14 | run: echo "RELEASE_VERSION=$(echo ${GITHUB_REF} | cut -d'/' -f3)" >> $GITHUB_ENV 15 | - name: Set env MESSAGE 16 | run: echo "MESSAGE=$(git tag -l --format='%(contents)' ${RELEASE_VERSION})" >> $GITHUB_ENV 17 | - name: Create Release 18 | id: create_release 19 | uses: actions/create-release@v1 20 | env: 21 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 22 | with: 23 | tag_name: ${{ github.ref }} 24 | release_name: ${{ env.RELEASE_VERSION }} 25 | body: ${{ env.MESSAGE }} 26 | draft: false 27 | prerelease: false 28 | 29 | assets: 30 | name: Push assets 31 | needs: release 32 | runs-on: ubuntu-latest 33 | steps: 34 | - name: Set up Go 1.13 35 | uses: actions/setup-go@v1 36 | with: 37 | go-version: 1.13 38 | id: go 39 | 40 | - name: Check out code 41 | uses: actions/checkout@v2 42 | - name: Set env 43 | run: | 44 | echo "RELEASE_VERSION=$(echo ${GITHUB_REF} | cut -d'/' -f3)" >> $GITHUB_ENV 45 | echo "REPO_NAME=$(echo ${GITHUB_REPOSITORY} | cut -d'/' -f2)" >> $GITHUB_ENV 46 | - name: Get dependencies 47 | run: | 48 | go get -v -t -d ./... 49 | - name: Build 50 | run: go build -o ${REPO_NAME} 51 | - name: Create archive 52 | run: tar -czvf ${REPO_NAME}_${RELEASE_VERSION}_linux_amd64.tar.gz ${REPO_NAME} 53 | - name: Upload archive to release 54 | uses: svenstaro/upload-release-action@v1-release 55 | with: 56 | repo_token: ${{ secrets.GITHUB_TOKEN }} 57 | file: ./${{ env.REPO_NAME }}_${{ env.RELEASE_VERSION }}_linux_amd64.tar.gz 58 | asset_name: ${{ env.REPO_NAME }}_${{ env.RELEASE_VERSION }}_linux_amd64.tar.gz 59 | tag: ${{ github.ref }} 60 | -------------------------------------------------------------------------------- /save.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "net/http" 7 | "os" 8 | "os/exec" 9 | "strings" 10 | "time" 11 | 12 | auth "github.com/abbot/go-http-auth" 13 | ) 14 | 15 | // GET /save/ 16 | func saveRules(w http.ResponseWriter, r *http.Request) { 17 | if *htpasswdfile != "" { 18 | htpasswd := auth.HtpasswdFileProvider(*htpasswdfile) 19 | authenticator := auth.NewBasicAuthenticator("Basic Realm", htpasswd) 20 | usercheck := authenticator.CheckAuth(r) 21 | if usercheck == "" { 22 | w.WriteHeader(http.StatusUnauthorized) 23 | 24 | return 25 | } 26 | } 27 | 28 | err := os.MkdirAll("/etc/iptables/", 0755) 29 | if err != nil { 30 | http.Error(w, err.Error(), 500) 31 | 32 | return 33 | } 34 | stdout, err := exec.Command("iptables-save").Output() 35 | if err != nil { 36 | http.Error(w, err.Error(), 500) 37 | 38 | return 39 | } 40 | err = ioutil.WriteFile("/etc/iptables/rules.v4", stdout, 0644) // nolint: gosec 41 | if err != nil { 42 | http.Error(w, err.Error(), 500) 43 | 44 | return 45 | } 46 | err = os.MkdirAll(*savePath, 0755) 47 | if err != nil { 48 | http.Error(w, err.Error(), 500) 49 | 50 | return 51 | } 52 | 53 | currentTime := time.Now().Local() 54 | dstFile := []string{*savePath, "rules.v4.", currentTime.Format("2006-01-02"), ".", currentTime.Format("15-04-05")} 55 | cmd := exec.Command("cp", "/etc/iptables/rules.v4", strings.Join(dstFile, "")) 56 | err = cmd.Run() 57 | if err != nil { 58 | http.Error(w, err.Error(), 500) 59 | 60 | return 61 | } 62 | fmt.Fprintln(w, strings.Join(dstFile, "")) 63 | } 64 | 65 | // GET /restore/{file} 66 | func restoreRules(w http.ResponseWriter, r *http.Request) { 67 | if *htpasswdfile != "" { 68 | htpasswd := auth.HtpasswdFileProvider(*htpasswdfile) 69 | authenticator := auth.NewBasicAuthenticator("Basic Realm", htpasswd) 70 | usercheck := authenticator.CheckAuth(r) 71 | if usercheck == "" { 72 | w.WriteHeader(http.StatusUnauthorized) 73 | 74 | return 75 | } 76 | } 77 | err := exec.Command("iptables-restore", r.URL.Query().Get("file")).Run() 78 | if err != nil { 79 | http.Error(w, err.Error(), 500) 80 | 81 | return 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /save_v6.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "net/http" 7 | "os" 8 | "os/exec" 9 | "strings" 10 | "time" 11 | 12 | auth "github.com/abbot/go-http-auth" 13 | ) 14 | 15 | // GET /save_v6/ 16 | func saveRulesV6(w http.ResponseWriter, r *http.Request) { 17 | if *htpasswdfile != "" { 18 | htpasswd := auth.HtpasswdFileProvider(*htpasswdfile) 19 | authenticator := auth.NewBasicAuthenticator("Basic Realm", htpasswd) 20 | usercheck := authenticator.CheckAuth(r) 21 | if usercheck == "" { 22 | w.WriteHeader(http.StatusUnauthorized) 23 | 24 | return 25 | } 26 | } 27 | 28 | err := os.MkdirAll("/etc/iptables/", 0755) 29 | if err != nil { 30 | http.Error(w, err.Error(), 500) 31 | 32 | return 33 | } 34 | stdout, err := exec.Command("ip6tables-save").Output() 35 | if err != nil { 36 | http.Error(w, err.Error(), 500) 37 | 38 | return 39 | } 40 | err = ioutil.WriteFile("/etc/iptables/rules.v6", stdout, 0644) // nolint: gosec 41 | if err != nil { 42 | http.Error(w, err.Error(), 500) 43 | 44 | return 45 | } 46 | err = os.MkdirAll(*savePath, 0755) 47 | if err != nil { 48 | http.Error(w, err.Error(), 500) 49 | 50 | return 51 | } 52 | currentTime := time.Now().Local() 53 | dstFile := []string{*savePath, "rules.v6.", currentTime.Format("2006-01-02"), ".", currentTime.Format("15-04-05")} 54 | cmd := exec.Command("cp", "/etc/iptables/rules.v6", strings.Join(dstFile, "")) 55 | err = cmd.Run() 56 | if err != nil { 57 | http.Error(w, err.Error(), 500) 58 | 59 | return 60 | } 61 | fmt.Fprintln(w, strings.Join(dstFile, "")) 62 | } 63 | 64 | // GET /restore_v6/{file} 65 | func restoreRulesV6(w http.ResponseWriter, r *http.Request) { 66 | if *htpasswdfile != "" { 67 | htpasswd := auth.HtpasswdFileProvider(*htpasswdfile) 68 | authenticator := auth.NewBasicAuthenticator("Basic Realm", htpasswd) 69 | usercheck := authenticator.CheckAuth(r) 70 | if usercheck == "" { 71 | w.WriteHeader(http.StatusUnauthorized) 72 | 73 | return 74 | } 75 | } 76 | err := exec.Command("ip6tables-restore", r.URL.Query().Get("file")).Run() 77 | if err != nil { 78 | http.Error(w, err.Error(), 500) 79 | 80 | return 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # iptables-api 2 | ![GitHub release (latest by date)](https://img.shields.io/github/v/release/jeremmfr/iptables-api) 3 | [![Go Status](https://github.com/jeremmfr/iptables-api/workflows/Go%20Tests/badge.svg)](https://github.com/jeremmfr/iptables-api/actions) 4 | [![Lint Status](https://github.com/jeremmfr/iptables-api/workflows/GolangCI-Lint/badge.svg)](https://github.com/jeremmfr/iptables-api/actions) 5 | [![Go Report Card](https://goreportcard.com/badge/github.com/jeremmfr/iptables-api)](https://goreportcard.com/report/github.com/jeremmfr/iptables-api) 6 | 7 | Create API REST for iptables command 8 | 9 | Compile: 10 | -------- 11 | export GO111MODULE=on 12 | go build -o iptables-api 13 | 14 | Run: 15 | ---- 16 | ./iptables-api -h 17 | Usage of /root/iptables-api: 18 | -cert string 19 | file of certificat for https 20 | -htpasswd string 21 | htpasswd file for login:password 22 | -https 23 | https = true or false 24 | -ip string 25 | listen on IP (default "127.0.0.1") 26 | -key string 27 | file of key for https 28 | -log string 29 | file for access log (default "/var/log/iptables-api.access.log") 30 | -port string 31 | listen on port (default "8080") 32 | -save_path string 33 | path for backups => /save (default "var/backups/iptables-api/") 34 | 35 | ./iptables-api -https -ip=192.168.0.1 -port=8443 -log=/var/log/iptables-api.access.log -cert=cert.pem -key=key.pem -htpasswd=/root/.htpasswd 36 | 37 | API List : 38 | --------- 39 | 40 | **Rules:** 41 | 42 | Test,Add,Del iptables rule in table filter with the parameters 43 | 44 | GET/PUT/DELETE /rules/{action}/{chain}/{proto}/{iface_in}/{iface_out}/{source}/{destination}/?sports=00&dports=00&state=XXXX&fragment=true&icmptype=XXXX&log-prefix=XXXXX 45 | 46 | with for source and destination _ instead / : 10.0.0.0_8 or range 10.0.0.0-10.255.0.0_32 47 | log-prefix only if action = LOG 48 | 49 | **Nat:** 50 | 51 | Test,Add,Del iptables rule in table nat with the parameters 52 | 53 | GET/PUT/DELETE /nat/{action}/{chain}/{proto}/{iface}/{source}/{destination}/{nat_final}/?dport=00&except=true 54 | 55 | with for source and destination _ instead / : 10.0.0.0_8 56 | 57 | **Raw:** 58 | 59 | Test,Add,Del iptables rule in table raw with the parameters 60 | 61 | GET/PUT/DELETE /raw/{action}/{chain}/{proto}/{iface_in}/{iface_out}/{source}/{destination}/?sports=00&dports=00&tcpflag1=XYZ&tcpflag2=Y¬rack=true&log-prefix=XXXXX 62 | 63 | with for source and destination _ instead / : 10.0.0.0_8 or range 10.0.0.0-10.255.0.0_32 64 | log-prefix only if action = LOG 65 | 66 | **Chain:** 67 | 68 | Test,Add,Del chain with the parameters 69 | 70 | GET/PUT/DELETE /chain/{table}/{name}/ 71 | 72 | Rename chain with the parameters 73 | 74 | PUT /mvchain/{table}/{oldname}/{newname}/ 75 | 76 | **Save:** 77 | 78 | iptables-save > /etc/iptables/rules.v4 && cp /etc/iptables/rules.v4 /var/backups/iptables-api/rules.v4.2006-01-02.15-04-05 79 | 80 | GET /save/ 81 | 82 | **Restore:** 83 | 84 | iptables-restore $file 85 | 86 | PUT /restore/{file} 87 | -------------------------------------------------------------------------------- /chain.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | 7 | auth "github.com/abbot/go-http-auth" 8 | "github.com/gorilla/mux" 9 | "github.com/jeremmfr/go-iptables/iptables" 10 | ) 11 | 12 | // PUT /chain/{table}/{name}/ 13 | func addChain(w http.ResponseWriter, r *http.Request) { 14 | if *htpasswdfile != "" { 15 | htpasswd := auth.HtpasswdFileProvider(*htpasswdfile) 16 | authenticator := auth.NewBasicAuthenticator("Basic Realm", htpasswd) 17 | usercheck := authenticator.CheckAuth(r) 18 | if usercheck == "" { 19 | w.WriteHeader(http.StatusUnauthorized) 20 | 21 | return 22 | } 23 | } 24 | vars := mux.Vars(r) 25 | 26 | ipt, err := iptables.New() 27 | if err != nil { 28 | http.Error(w, err.Error(), 500) 29 | 30 | return 31 | } 32 | respErr = ipt.NewChain(vars["table"], vars["name"]) 33 | if respErr != nil { 34 | w.WriteHeader(http.StatusBadRequest) 35 | fmt.Fprintln(w, respErr) 36 | } 37 | } 38 | 39 | // DELETE /chain/{table}/{name}/ 40 | func delChain(w http.ResponseWriter, r *http.Request) { 41 | if *htpasswdfile != "" { 42 | htpasswd := auth.HtpasswdFileProvider(*htpasswdfile) 43 | authenticator := auth.NewBasicAuthenticator("Basic Realm", htpasswd) 44 | usercheck := authenticator.CheckAuth(r) 45 | if usercheck == "" { 46 | w.WriteHeader(http.StatusUnauthorized) 47 | 48 | return 49 | } 50 | } 51 | vars := mux.Vars(r) 52 | 53 | ipt, err := iptables.New() 54 | if err != nil { 55 | http.Error(w, err.Error(), 500) 56 | 57 | return 58 | } 59 | // Clear chain before delete 60 | respErr = ipt.ClearChain(vars["table"], vars["name"]) 61 | if respErr != nil { 62 | w.WriteHeader(http.StatusBadRequest) 63 | fmt.Fprintln(w, respErr) 64 | } 65 | // Delete chain 66 | respErr = ipt.DeleteChain(vars["table"], vars["name"]) 67 | if respErr != nil { 68 | w.WriteHeader(http.StatusBadRequest) 69 | fmt.Fprintln(w, respErr) 70 | } 71 | } 72 | 73 | // GET /chain/{table}/{name}/ 74 | func listChain(w http.ResponseWriter, r *http.Request) { 75 | if *htpasswdfile != "" { 76 | htpasswd := auth.HtpasswdFileProvider(*htpasswdfile) 77 | authenticator := auth.NewBasicAuthenticator("Basic Realm", htpasswd) 78 | usercheck := authenticator.CheckAuth(r) 79 | if usercheck == "" { 80 | w.WriteHeader(http.StatusUnauthorized) 81 | 82 | return 83 | } 84 | } 85 | vars := mux.Vars(r) 86 | 87 | ipt, err := iptables.New() 88 | if err != nil { 89 | http.Error(w, err.Error(), 500) 90 | 91 | return 92 | } 93 | respStr, respErr := ipt.List(vars["table"], vars["name"]) 94 | if respErr != nil { 95 | w.WriteHeader(http.StatusBadRequest) 96 | fmt.Fprintln(w, respErr) 97 | } 98 | for i := 0; i < len(respStr); i++ { 99 | fmt.Fprintln(w, respStr[i]) 100 | } 101 | } 102 | 103 | // PUT /mvchain/{table}/{oldname}/{newname}/ 104 | func renameChain(w http.ResponseWriter, r *http.Request) { 105 | if *htpasswdfile != "" { 106 | htpasswd := auth.HtpasswdFileProvider(*htpasswdfile) 107 | authenticator := auth.NewBasicAuthenticator("Basic Realm", htpasswd) 108 | usercheck := authenticator.CheckAuth(r) 109 | if usercheck == "" { 110 | w.WriteHeader(http.StatusUnauthorized) 111 | 112 | return 113 | } 114 | } 115 | vars := mux.Vars(r) 116 | 117 | ipt, err := iptables.New() 118 | if err != nil { 119 | http.Error(w, err.Error(), 500) 120 | 121 | return 122 | } 123 | respErr = ipt.RenameChain(vars["table"], vars["oldname"], vars["newname"]) 124 | if respErr != nil { 125 | w.WriteHeader(http.StatusBadRequest) 126 | fmt.Fprintln(w, respErr) 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /chain_v6.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | 7 | auth "github.com/abbot/go-http-auth" 8 | "github.com/gorilla/mux" 9 | "github.com/jeremmfr/go-iptables/iptables" 10 | ) 11 | 12 | // PUT /chain_v6/{table}/{name}/ 13 | func addChainV6(w http.ResponseWriter, r *http.Request) { 14 | if *htpasswdfile != "" { 15 | htpasswd := auth.HtpasswdFileProvider(*htpasswdfile) 16 | authenticator := auth.NewBasicAuthenticator("Basic Realm", htpasswd) 17 | usercheck := authenticator.CheckAuth(r) 18 | if usercheck == "" { 19 | w.WriteHeader(http.StatusUnauthorized) 20 | 21 | return 22 | } 23 | } 24 | vars := mux.Vars(r) 25 | 26 | ipt, err := iptables.NewWithProtocol(v6) 27 | if err != nil { 28 | http.Error(w, err.Error(), 500) 29 | 30 | return 31 | } 32 | respErr = ipt.NewChain(vars["table"], vars["name"]) 33 | if respErr != nil { 34 | w.WriteHeader(http.StatusBadRequest) 35 | fmt.Fprintln(w, respErr) 36 | } 37 | } 38 | 39 | // DELETE /chain_v6/{table}/{name}/ 40 | func delChainV6(w http.ResponseWriter, r *http.Request) { 41 | if *htpasswdfile != "" { 42 | htpasswd := auth.HtpasswdFileProvider(*htpasswdfile) 43 | authenticator := auth.NewBasicAuthenticator("Basic Realm", htpasswd) 44 | usercheck := authenticator.CheckAuth(r) 45 | if usercheck == "" { 46 | w.WriteHeader(http.StatusUnauthorized) 47 | 48 | return 49 | } 50 | } 51 | vars := mux.Vars(r) 52 | 53 | ipt, err := iptables.NewWithProtocol(v6) 54 | if err != nil { 55 | http.Error(w, err.Error(), 500) 56 | 57 | return 58 | } 59 | 60 | // Clear chain before delete 61 | respErr = ipt.ClearChain(vars["table"], vars["name"]) 62 | if respErr != nil { 63 | w.WriteHeader(http.StatusBadRequest) 64 | fmt.Fprintln(w, respErr) 65 | } 66 | // Delete chain 67 | respErr = ipt.DeleteChain(vars["table"], vars["name"]) 68 | if respErr != nil { 69 | w.WriteHeader(http.StatusBadRequest) 70 | fmt.Fprintln(w, respErr) 71 | } 72 | } 73 | 74 | // GET /chain_v6/{table}/{name}/ 75 | func listChainV6(w http.ResponseWriter, r *http.Request) { 76 | if *htpasswdfile != "" { 77 | htpasswd := auth.HtpasswdFileProvider(*htpasswdfile) 78 | authenticator := auth.NewBasicAuthenticator("Basic Realm", htpasswd) 79 | usercheck := authenticator.CheckAuth(r) 80 | if usercheck == "" { 81 | w.WriteHeader(http.StatusUnauthorized) 82 | 83 | return 84 | } 85 | } 86 | vars := mux.Vars(r) 87 | 88 | ipt, err := iptables.NewWithProtocol(v6) 89 | if err != nil { 90 | http.Error(w, err.Error(), 500) 91 | 92 | return 93 | } 94 | respStr, respErr := ipt.List(vars["table"], vars["name"]) 95 | if respErr != nil { 96 | w.WriteHeader(http.StatusBadRequest) 97 | fmt.Fprintln(w, respErr) 98 | } 99 | for i := 0; i < len(respStr); i++ { 100 | fmt.Fprintln(w, respStr[i]) 101 | } 102 | } 103 | 104 | // PUT /mvchain_v6/{table}/{oldname}/{newname}/ 105 | func renameChainV6(w http.ResponseWriter, r *http.Request) { 106 | if *htpasswdfile != "" { 107 | htpasswd := auth.HtpasswdFileProvider(*htpasswdfile) 108 | authenticator := auth.NewBasicAuthenticator("Basic Realm", htpasswd) 109 | usercheck := authenticator.CheckAuth(r) 110 | if usercheck == "" { 111 | w.WriteHeader(http.StatusUnauthorized) 112 | 113 | return 114 | } 115 | } 116 | vars := mux.Vars(r) 117 | 118 | ipt, err := iptables.NewWithProtocol(v6) 119 | if err != nil { 120 | http.Error(w, err.Error(), 500) 121 | 122 | return 123 | } 124 | respErr = ipt.RenameChain(vars["table"], vars["oldname"], vars["newname"]) 125 | if respErr != nil { 126 | w.WriteHeader(http.StatusBadRequest) 127 | fmt.Fprintln(w, respErr) 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "log" 6 | "net/http" 7 | "os" 8 | "strings" 9 | 10 | "github.com/gorilla/handlers" 11 | "github.com/gorilla/mux" 12 | "github.com/jeremmfr/go-iptables/iptables" 13 | ) 14 | 15 | const ( 16 | // 17 | v6 iptables.Protocol = iota + 1 18 | dnatAct string = "dnat" 19 | snatAct string = "snat" 20 | logAct string = "LOG" 21 | trueStr string = "true" 22 | tcpStr string = "tcp" 23 | SYNStr string = "SYN" 24 | defaultFlagsMask string = "FIN,SYN,RST,ACK" 25 | defaultFlagsMask2 string = "SYN,RST,ACK,FIN" 26 | ) 27 | 28 | var ( 29 | respErr error 30 | htpasswdfile *string 31 | savePath *string 32 | ) 33 | 34 | func main() { 35 | listenIP := flag.String("ip", "127.0.0.1", "listen on IP") 36 | listenPort := flag.String("port", "8080", "listen on port") 37 | https := flag.Bool("https", false, "https = true or false") 38 | cert := flag.String("cert", "", "file of certificat for https") 39 | key := flag.String("key", "", "file of key for https") 40 | accessLogFile := flag.String("log", "/var/log/iptables-api.access.log", "file for access log") 41 | htpasswdfile = flag.String("htpasswd", "", "htpasswd file for login:password") 42 | savePath = flag.String("savepath", "/var/backups/iptables-api/", "path for backups file on /save") 43 | 44 | flag.Parse() 45 | 46 | // accesslog file open 47 | accessLog, err := os.OpenFile(*accessLogFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644) 48 | if err != nil { 49 | log.Fatalf("Failed to open access log: %s", err) 50 | } 51 | 52 | // create router 53 | router := mux.NewRouter().StrictSlash(true) 54 | 55 | // ipv4 api 56 | router.HandleFunc("/rules/{action}/{chain}/{proto}/{iface_in}/{iface_out}/{source}/{destination}/", addRules).Methods("PUT") 57 | router.HandleFunc("/rules/{action}/{chain}/{proto}/{iface_in}/{iface_out}/{source}/{destination}/", delRules).Methods("DELETE") 58 | router.HandleFunc("/rules/{action}/{chain}/{proto}/{iface_in}/{iface_out}/{source}/{destination}/", checkRules).Methods("GET") 59 | router.HandleFunc("/raw/{action}/{chain}/{proto}/{iface_in}/{iface_out}/{source}/{destination}/", addRaw).Methods("PUT") 60 | router.HandleFunc("/raw/{action}/{chain}/{proto}/{iface_in}/{iface_out}/{source}/{destination}/", delRaw).Methods("DELETE") 61 | router.HandleFunc("/raw/{action}/{chain}/{proto}/{iface_in}/{iface_out}/{source}/{destination}/", checkRaw).Methods("GET") 62 | router.HandleFunc("/nat/{action}/{chain}/{proto}/{iface}/{source}/{destination}/{nat_final}/", addNat).Methods("PUT") 63 | router.HandleFunc("/nat/{action}/{chain}/{proto}/{iface}/{source}/{destination}/{nat_final}/", delNat).Methods("DELETE") 64 | router.HandleFunc("/nat/{action}/{chain}/{proto}/{iface}/{source}/{destination}/{nat_final}/", checkNat).Methods("GET") 65 | router.HandleFunc("/chain/{table}/{name}/", addChain).Methods("PUT") 66 | router.HandleFunc("/chain/{table}/{name}/", delChain).Methods("DELETE") 67 | router.HandleFunc("/chain/{table}/{name}/", listChain).Methods("GET") 68 | router.HandleFunc("/mvchain/{table}/{oldname}/{newname}/", renameChain).Methods("PUT") 69 | router.HandleFunc("/save/", saveRules).Methods("GET") 70 | router.HandleFunc("/restore/", restoreRules).Methods("PUT") 71 | 72 | // ipv6 api 73 | router.HandleFunc("/rules_v6/{action}/{chain}/{proto}/{iface_in}/{iface_out}/{source}/{destination}/", addRulesV6).Methods("PUT") 74 | router.HandleFunc("/rules_v6/{action}/{chain}/{proto}/{iface_in}/{iface_out}/{source}/{destination}/", delRulesV6).Methods("DELETE") 75 | router.HandleFunc("/rules_v6/{action}/{chain}/{proto}/{iface_in}/{iface_out}/{source}/{destination}/", checkRulesV6).Methods("GET") 76 | router.HandleFunc("/raw_v6/{action}/{chain}/{proto}/{iface_in}/{iface_out}/{source}/{destination}/", addRawV6).Methods("PUT") 77 | router.HandleFunc("/raw_v6/{action}/{chain}/{proto}/{iface_in}/{iface_out}/{source}/{destination}/", delRawV6).Methods("DELETE") 78 | router.HandleFunc("/raw_v6/{action}/{chain}/{proto}/{iface_in}/{iface_out}/{source}/{destination}/", checkRawV6).Methods("GET") 79 | router.HandleFunc("/nat_v6/{action}/{chain}/{proto}/{iface}/{source}/{destination}/{nat_final}/", addNatV6).Methods("PUT") 80 | router.HandleFunc("/nat_v6/{action}/{chain}/{proto}/{iface}/{source}/{destination}/{nat_final}/", delNatV6).Methods("DELETE") 81 | router.HandleFunc("/nat_v6/{action}/{chain}/{proto}/{iface}/{source}/{destination}/{nat_final}/", checkNatV6).Methods("GET") 82 | router.HandleFunc("/chain_v6/{table}/{name}/", addChainV6).Methods("PUT") 83 | router.HandleFunc("/chain_v6/{table}/{name}/", delChainV6).Methods("DELETE") 84 | router.HandleFunc("/chain_v6/{table}/{name}/", listChainV6).Methods("GET") 85 | router.HandleFunc("/mvchain_v6/{table}/{oldname}/{newname}/", renameChainV6).Methods("PUT") 86 | router.HandleFunc("/save_v6/", saveRulesV6).Methods("GET") 87 | router.HandleFunc("/restore_v6/", restoreRulesV6).Methods("PUT") 88 | 89 | loggedRouter := handlers.CombinedLoggingHandler(accessLog, router) 90 | 91 | if *https { 92 | if (*cert == "") || (*key == "") { 93 | log.Fatalf("HTTPS true but no cert and key defined") 94 | } else { 95 | log.Fatal(http.ListenAndServeTLS(strings.Join([]string{*listenIP, ":", *listenPort}, ""), *cert, *key, loggedRouter)) 96 | } 97 | } else { 98 | log.Fatal(http.ListenAndServe(strings.Join([]string{*listenIP, ":", *listenPort}, ""), loggedRouter)) 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /rules_v6.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "reflect" 7 | "strconv" 8 | "strings" 9 | 10 | auth "github.com/abbot/go-http-auth" 11 | "github.com/gorilla/mux" 12 | "github.com/jeremmfr/go-iptables/iptables" 13 | ) 14 | 15 | func ruleGenerateV6(r *http.Request) []string { 16 | vars := mux.Vars(r) 17 | var specEnd []string 18 | 19 | if r.URL.Query().Get("sports") != "" { 20 | specEnd = append(specEnd, "-m", "multiport", "--sports", r.URL.Query().Get("sports")) 21 | } 22 | if r.URL.Query().Get("dports") != "" { 23 | specEnd = append(specEnd, "-m", "multiport", "--dports", r.URL.Query().Get("dports")) 24 | } 25 | if r.URL.Query().Get("state") != "" { 26 | specEnd = append(specEnd, "-m", "state", "--state", r.URL.Query().Get("state")) 27 | } 28 | if r.URL.Query().Get("icmptype") != "" { 29 | specEnd = append(specEnd, "--icmpv6-type", r.URL.Query().Get("icmptype")) 30 | } 31 | if vars["iface_in"] != "*" { 32 | specEnd = append(specEnd, "-i", vars["iface_in"]) 33 | } 34 | if vars["iface_out"] != "*" { 35 | specEnd = append(specEnd, "-o", vars["iface_out"]) 36 | } 37 | srcRange := strings.Contains(vars["source"], "-") 38 | dstRange := strings.Contains(vars["destination"], "-") 39 | ruleSpecs := []string{"-p", vars["proto"]} 40 | if srcRange { 41 | ruleSpecs = append(ruleSpecs, "-m", "iprange", "--src-range", strings.ReplaceAll(vars["source"], "_128", "")) 42 | } else { 43 | ruleSpecs = append(ruleSpecs, "-s", strings.ReplaceAll(vars["source"], "_", "/")) 44 | } 45 | if dstRange { 46 | ruleSpecs = append(ruleSpecs, "-m", "iprange", "--dst-range", strings.ReplaceAll(vars["destination"], "_128", "")) 47 | } else { 48 | ruleSpecs = append(ruleSpecs, "-d", strings.ReplaceAll(vars["destination"], "_", "/")) 49 | } 50 | ruleSpecs = append(ruleSpecs, "-j", vars["action"]) 51 | if (r.URL.Query().Get("log-prefix") != "") && vars["action"] == logAct { 52 | ruleSpecs = append(ruleSpecs, "--log-prefix", r.URL.Query().Get("log-prefix")) 53 | } 54 | ruleSpecs = append(ruleSpecs, specEnd...) 55 | 56 | return ruleSpecs 57 | } 58 | 59 | func checkPosRulesV6(r *http.Request) ([]string, error) { 60 | vars := mux.Vars(r) 61 | var linenumber []string 62 | 63 | line := []string{vars["action"], vars["proto"]} 64 | line = append(line, vars["iface_in"], vars["iface_out"]) 65 | 66 | srcRange := strings.Contains(vars["source"], "-") 67 | if srcRange { 68 | line = append(line, "::/0") 69 | } else { 70 | source128 := strings.Contains(vars["source"], "_128") 71 | if source128 { 72 | line = append(line, strings.ReplaceAll(vars["source"], "_128", "")) 73 | } else { 74 | line = append(line, strings.ReplaceAll(vars["source"], "_", "/")) 75 | } 76 | } 77 | 78 | dstRange := strings.Contains(vars["destination"], "-") 79 | if dstRange { 80 | line = append(line, "::/0") 81 | } else { 82 | destination128 := strings.Contains(vars["destination"], "_128") 83 | if destination128 { 84 | line = append(line, strings.ReplaceAll(vars["destination"], "_128", "")) 85 | } else { 86 | line = append(line, strings.ReplaceAll(vars["destination"], "_", "/")) 87 | } 88 | } 89 | if srcRange { 90 | line = append(line, "source", "IP", "range", strings.ReplaceAll(vars["source"], "_128", "")) 91 | } 92 | if dstRange { 93 | line = append(line, "destination", "IP", "range", strings.ReplaceAll(vars["destination"], "_128", "")) 94 | } 95 | if r.URL.Query().Get("sports") != "" { 96 | line = append(line, "multiport", "sports", r.URL.Query().Get("sports")) 97 | } 98 | if r.URL.Query().Get("dports") != "" { 99 | line = append(line, "multiport", "dports", r.URL.Query().Get("dports")) 100 | } 101 | if r.URL.Query().Get("icmptype") != "" { 102 | line = append(line, "ipv6-icmptype", r.URL.Query().Get("icmptype")) 103 | } 104 | if (r.URL.Query().Get("log-prefix") != "") && vars["action"] == logAct { 105 | line = append(line, "LOG", "flags", "0", "level", "4", "prefix", strings.Join([]string{"\"", r.URL.Query().Get("log-prefix"), "\""}, "")) 106 | } 107 | ipt, err := iptables.NewWithProtocol(v6) 108 | if err != nil { 109 | return nil, err 110 | } 111 | args := []string{"-t", "filter", "-vnL", vars["chain"], "--line-numbers"} 112 | if ipt.HasWait { 113 | args = append(args, "--wait") 114 | } 115 | rules, err := ipt.ExecuteList(args) 116 | if err != nil { 117 | return nil, err 118 | } 119 | for i := 0; i < len(rules); i++ { 120 | rulesSlice := strings.Fields(rules[i]) 121 | rulesSliceNoVerb := rulesSlice[3:] 122 | if reflect.DeepEqual(line, rulesSliceNoVerb) { 123 | linenumber = append(linenumber, rulesSlice[0]) 124 | } 125 | } 126 | 127 | return linenumber, nil 128 | } 129 | 130 | // PUT /rules_v6/{action}/{chain}/{proto}/{iface_in}/{iface_out}/{source}/{destination}/?sports=00&dports=00 131 | func addRulesV6(w http.ResponseWriter, r *http.Request) { 132 | if *htpasswdfile != "" { 133 | htpasswd := auth.HtpasswdFileProvider(*htpasswdfile) 134 | authenticator := auth.NewBasicAuthenticator("Basic Realm", htpasswd) 135 | usercheck := authenticator.CheckAuth(r) 136 | if usercheck == "" { 137 | w.WriteHeader(http.StatusUnauthorized) 138 | 139 | return 140 | } 141 | } 142 | rulespecs := ruleGenerateV6(r) 143 | ipt, err := iptables.NewWithProtocol(v6) 144 | if err != nil { 145 | http.Error(w, err.Error(), 500) 146 | 147 | return 148 | } 149 | if ipt.HasWait { 150 | rulespecs = append(rulespecs, "--wait") 151 | } 152 | vars := mux.Vars(r) 153 | if r.URL.Query().Get("position") != "" { 154 | position, err := strconv.Atoi(r.URL.Query().Get("position")) 155 | if err != nil { 156 | http.Error(w, err.Error(), 500) 157 | 158 | return 159 | } 160 | respErr = ipt.Insert("filter", vars["chain"], position, rulespecs...) 161 | } else { 162 | respErr = ipt.Append("filter", vars["chain"], rulespecs...) 163 | } 164 | if respErr != nil { 165 | w.WriteHeader(http.StatusBadRequest) 166 | fmt.Fprintln(w, respErr) 167 | } 168 | } 169 | 170 | // DELETE /rules_v6/{action}/{chain}/{proto}/{iface_in}/{iface_out}/{source}/{destination}/?sports=00&dports=00 171 | func delRulesV6(w http.ResponseWriter, r *http.Request) { 172 | if *htpasswdfile != "" { 173 | htpasswd := auth.HtpasswdFileProvider(*htpasswdfile) 174 | authenticator := auth.NewBasicAuthenticator("Basic Realm", htpasswd) 175 | usercheck := authenticator.CheckAuth(r) 176 | if usercheck == "" { 177 | w.WriteHeader(http.StatusUnauthorized) 178 | 179 | return 180 | } 181 | } 182 | rulespecs := ruleGenerateV6(r) 183 | ipt, err := iptables.NewWithProtocol(v6) 184 | if err != nil { 185 | http.Error(w, err.Error(), 500) 186 | 187 | return 188 | } 189 | if ipt.HasWait { 190 | rulespecs = append(rulespecs, "--wait") 191 | } 192 | vars := mux.Vars(r) 193 | respErr = ipt.Delete("filter", vars["chain"], rulespecs...) 194 | if respErr != nil { 195 | w.WriteHeader(http.StatusBadRequest) 196 | fmt.Fprintln(w, respErr) 197 | } 198 | } 199 | 200 | // GET /rules_v6/{action}/{chain}/{proto}/{iface_in}/{iface_out}/{source}/{destination}/?sports=00&dports=00 201 | func checkRulesV6(w http.ResponseWriter, r *http.Request) { 202 | if *htpasswdfile != "" { 203 | htpasswd := auth.HtpasswdFileProvider(*htpasswdfile) 204 | authenticator := auth.NewBasicAuthenticator("Basic Realm", htpasswd) 205 | usercheck := authenticator.CheckAuth(r) 206 | if usercheck == "" { 207 | w.WriteHeader(http.StatusUnauthorized) 208 | 209 | return 210 | } 211 | } 212 | rulespecs := ruleGenerateV6(r) 213 | ipt, err := iptables.NewWithProtocol(v6) 214 | if err != nil { 215 | http.Error(w, err.Error(), 500) 216 | 217 | return 218 | } 219 | if ipt.HasWait { 220 | rulespecs = append(rulespecs, "--wait") 221 | } 222 | if r.URL.Query().Get("position") != "" { 223 | posRules, err := checkPosRulesV6(r) 224 | if err != nil { 225 | http.Error(w, err.Error(), 500) 226 | 227 | return 228 | } 229 | switch { 230 | case len(posRules) == 0: 231 | w.WriteHeader(http.StatusNotFound) 232 | 233 | return 234 | case len(posRules) != 1: 235 | w.WriteHeader(http.StatusConflict) 236 | 237 | return 238 | case posRules[0] == r.URL.Query().Get("position"): 239 | return 240 | default: 241 | w.WriteHeader(http.StatusNotFound) 242 | 243 | return 244 | } 245 | } else { 246 | vars := mux.Vars(r) 247 | respStr, respErr := ipt.Exists("filter", vars["chain"], rulespecs...) 248 | if respErr != nil { 249 | w.WriteHeader(http.StatusBadRequest) 250 | fmt.Fprintln(w, respErr) 251 | 252 | return 253 | } 254 | if !respStr { 255 | w.WriteHeader(http.StatusNotFound) 256 | 257 | return 258 | } 259 | } 260 | } 261 | -------------------------------------------------------------------------------- /rules.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "reflect" 7 | "strconv" 8 | "strings" 9 | 10 | auth "github.com/abbot/go-http-auth" 11 | "github.com/gorilla/mux" 12 | "github.com/jeremmfr/go-iptables/iptables" 13 | ) 14 | 15 | func ruleGenerate(r *http.Request) []string { 16 | vars := mux.Vars(r) 17 | var specEnd []string 18 | 19 | if r.URL.Query().Get("sports") != "" { 20 | specEnd = append(specEnd, "-m", "multiport", "--sports", r.URL.Query().Get("sports")) 21 | } 22 | if r.URL.Query().Get("dports") != "" { 23 | specEnd = append(specEnd, "-m", "multiport", "--dports", r.URL.Query().Get("dports")) 24 | } 25 | if r.URL.Query().Get("state") != "" { 26 | specEnd = append(specEnd, "-m", "state", "--state", r.URL.Query().Get("state")) 27 | } 28 | if r.URL.Query().Get("fragment") != "" { 29 | specEnd = append(specEnd, "-f") 30 | } 31 | if r.URL.Query().Get("icmptype") != "" { 32 | specEnd = append(specEnd, "--icmp-type", r.URL.Query().Get("icmptype")) 33 | } 34 | if vars["iface_in"] != "*" { 35 | specEnd = append(specEnd, "-i", vars["iface_in"]) 36 | } 37 | if vars["iface_out"] != "*" { 38 | specEnd = append(specEnd, "-o", vars["iface_out"]) 39 | } 40 | srcRange := strings.Contains(vars["source"], "-") 41 | dstRange := strings.Contains(vars["destination"], "-") 42 | ruleSpecs := []string{"-p", vars["proto"]} 43 | if srcRange { 44 | ruleSpecs = append(ruleSpecs, "-m", "iprange", "--src-range", strings.ReplaceAll(vars["source"], "_32", "")) 45 | } else { 46 | ruleSpecs = append(ruleSpecs, "-s", strings.ReplaceAll(vars["source"], "_", "/")) 47 | } 48 | if dstRange { 49 | ruleSpecs = append(ruleSpecs, "-m", "iprange", "--dst-range", strings.ReplaceAll(vars["destination"], "_32", "")) 50 | } else { 51 | ruleSpecs = append(ruleSpecs, "-d", strings.ReplaceAll(vars["destination"], "_", "/")) 52 | } 53 | ruleSpecs = append(ruleSpecs, "-j", vars["action"]) 54 | if (r.URL.Query().Get("log-prefix") != "") && vars["action"] == logAct { 55 | ruleSpecs = append(ruleSpecs, "--log-prefix", r.URL.Query().Get("log-prefix")) 56 | } 57 | ruleSpecs = append(ruleSpecs, specEnd...) 58 | 59 | return ruleSpecs 60 | } 61 | 62 | func checkPosRules(r *http.Request) ([]string, error) { 63 | vars := mux.Vars(r) 64 | var linenumber []string 65 | 66 | line := []string{vars["action"], vars["proto"]} 67 | if r.URL.Query().Get("fragment") != "" { 68 | line = append(line, "-f") 69 | } else { 70 | line = append(line, "--") 71 | } 72 | line = append(line, vars["iface_in"], vars["iface_out"]) 73 | 74 | srcRange := strings.Contains(vars["source"], "-") 75 | if srcRange { 76 | line = append(line, "0.0.0.0/0") 77 | } else { 78 | source32 := strings.Contains(vars["source"], "_32") 79 | if source32 { 80 | line = append(line, strings.ReplaceAll(vars["source"], "_32", "")) 81 | } else { 82 | line = append(line, strings.ReplaceAll(vars["source"], "_", "/")) 83 | } 84 | } 85 | 86 | dstRange := strings.Contains(vars["destination"], "-") 87 | if dstRange { 88 | line = append(line, "0.0.0.0/0") 89 | } else { 90 | destination32 := strings.Contains(vars["destination"], "_32") 91 | if destination32 { 92 | line = append(line, strings.ReplaceAll(vars["destination"], "_32", "")) 93 | } else { 94 | line = append(line, strings.ReplaceAll(vars["destination"], "_", "/")) 95 | } 96 | } 97 | if srcRange { 98 | line = append(line, "source", "IP", "range", strings.ReplaceAll(vars["source"], "_32", "")) 99 | } 100 | if dstRange { 101 | line = append(line, "destination", "IP", "range", strings.ReplaceAll(vars["destination"], "_32", "")) 102 | } 103 | if r.URL.Query().Get("sports") != "" { 104 | line = append(line, "multiport", "sports", r.URL.Query().Get("sports")) 105 | } 106 | if r.URL.Query().Get("dports") != "" { 107 | line = append(line, "multiport", "dports", r.URL.Query().Get("dports")) 108 | } 109 | if r.URL.Query().Get("icmptype") != "" { 110 | line = append(line, "icmptype", r.URL.Query().Get("icmptype")) 111 | } 112 | if (r.URL.Query().Get("log-prefix") != "") && vars["action"] == logAct { 113 | line = append(line, "LOG", "flags", "0", "level", "4", "prefix", strings.Join([]string{"\"", r.URL.Query().Get("log-prefix"), "\""}, "")) 114 | } 115 | if vars["action"] == "REJECT" { 116 | line = append(line, "reject-with", "icmp-port-unreachable") 117 | } 118 | ipt, err := iptables.New() 119 | if err != nil { 120 | return nil, err 121 | } 122 | args := []string{"-t", "filter", "-vnL", vars["chain"], "--line-numbers"} 123 | if ipt.HasWait { 124 | args = append(args, "--wait") 125 | } 126 | rules, err := ipt.ExecuteList(args) 127 | if err != nil { 128 | return nil, err 129 | } 130 | for i := 0; i < len(rules); i++ { 131 | rulesSlice := strings.Fields(rules[i]) 132 | rulesSliceNoVerb := rulesSlice[3:] 133 | if reflect.DeepEqual(line, rulesSliceNoVerb) { 134 | linenumber = append(linenumber, rulesSlice[0]) 135 | } 136 | } 137 | 138 | return linenumber, nil 139 | } 140 | 141 | // PUT /rules/{action}/{chain}/{proto}/{iface_in}/{iface_out}/{source}/{destination}/?sports=00&dports=00 142 | func addRules(w http.ResponseWriter, r *http.Request) { 143 | if *htpasswdfile != "" { 144 | htpasswd := auth.HtpasswdFileProvider(*htpasswdfile) 145 | authenticator := auth.NewBasicAuthenticator("Basic Realm", htpasswd) 146 | usercheck := authenticator.CheckAuth(r) 147 | if usercheck == "" { 148 | w.WriteHeader(http.StatusUnauthorized) 149 | 150 | return 151 | } 152 | } 153 | rulespecs := ruleGenerate(r) 154 | ipt, err := iptables.New() 155 | if err != nil { 156 | http.Error(w, err.Error(), 500) 157 | 158 | return 159 | } 160 | if ipt.HasWait { 161 | rulespecs = append(rulespecs, "--wait") 162 | } 163 | vars := mux.Vars(r) 164 | if r.URL.Query().Get("position") != "" { 165 | position, err := strconv.Atoi(r.URL.Query().Get("position")) 166 | if err != nil { 167 | http.Error(w, err.Error(), 500) 168 | 169 | return 170 | } 171 | respErr = ipt.Insert("filter", vars["chain"], position, rulespecs...) 172 | } else { 173 | respErr = ipt.Append("filter", vars["chain"], rulespecs...) 174 | } 175 | if respErr != nil { 176 | w.WriteHeader(http.StatusBadRequest) 177 | fmt.Fprintln(w, respErr) 178 | } 179 | } 180 | 181 | // DELETE /rules/{action}/{chain}/{proto}/{iface_in}/{iface_out}/{source}/{destination}/?sports=00&dports=00 182 | func delRules(w http.ResponseWriter, r *http.Request) { 183 | if *htpasswdfile != "" { 184 | htpasswd := auth.HtpasswdFileProvider(*htpasswdfile) 185 | authenticator := auth.NewBasicAuthenticator("Basic Realm", htpasswd) 186 | usercheck := authenticator.CheckAuth(r) 187 | if usercheck == "" { 188 | w.WriteHeader(http.StatusUnauthorized) 189 | 190 | return 191 | } 192 | } 193 | rulespecs := ruleGenerate(r) 194 | ipt, err := iptables.New() 195 | if err != nil { 196 | http.Error(w, err.Error(), 500) 197 | 198 | return 199 | } 200 | if ipt.HasWait { 201 | rulespecs = append(rulespecs, "--wait") 202 | } 203 | vars := mux.Vars(r) 204 | respErr = ipt.Delete("filter", vars["chain"], rulespecs...) 205 | if respErr != nil { 206 | w.WriteHeader(http.StatusBadRequest) 207 | fmt.Fprintln(w, respErr) 208 | } 209 | } 210 | 211 | // GET /rules/{action}/{chain}/{proto}/{iface_in}/{iface_out}/{source}/{destination}/?sports=00&dports=00 212 | func checkRules(w http.ResponseWriter, r *http.Request) { 213 | if *htpasswdfile != "" { 214 | htpasswd := auth.HtpasswdFileProvider(*htpasswdfile) 215 | authenticator := auth.NewBasicAuthenticator("Basic Realm", htpasswd) 216 | usercheck := authenticator.CheckAuth(r) 217 | if usercheck == "" { 218 | w.WriteHeader(http.StatusUnauthorized) 219 | 220 | return 221 | } 222 | } 223 | rulespecs := ruleGenerate(r) 224 | ipt, err := iptables.New() 225 | if err != nil { 226 | http.Error(w, err.Error(), 500) 227 | 228 | return 229 | } 230 | if ipt.HasWait { 231 | rulespecs = append(rulespecs, "--wait") 232 | } 233 | if r.URL.Query().Get("position") != "" { 234 | posRules, err := checkPosRules(r) 235 | if err != nil { 236 | http.Error(w, err.Error(), 500) 237 | 238 | return 239 | } 240 | switch { 241 | case len(posRules) == 0: 242 | w.WriteHeader(http.StatusNotFound) 243 | 244 | return 245 | case len(posRules) != 1: 246 | w.WriteHeader(http.StatusConflict) 247 | 248 | return 249 | case posRules[0] == r.URL.Query().Get("position"): 250 | return 251 | default: 252 | w.WriteHeader(http.StatusNotFound) 253 | 254 | return 255 | } 256 | } else { 257 | vars := mux.Vars(r) 258 | respStr, respErr := ipt.Exists("filter", vars["chain"], rulespecs...) 259 | if respErr != nil { 260 | w.WriteHeader(http.StatusBadRequest) 261 | fmt.Fprintln(w, respErr) 262 | 263 | return 264 | } 265 | if !respStr { 266 | w.WriteHeader(http.StatusNotFound) 267 | 268 | return 269 | } 270 | } 271 | } 272 | -------------------------------------------------------------------------------- /raw.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "reflect" 7 | "strconv" 8 | "strings" 9 | 10 | auth "github.com/abbot/go-http-auth" 11 | "github.com/gorilla/mux" 12 | "github.com/jeremmfr/go-iptables/iptables" 13 | ) 14 | 15 | func rawGenerate(r *http.Request) []string { 16 | vars := mux.Vars(r) 17 | var specEnd []string 18 | 19 | if r.URL.Query().Get("sports") != "" { 20 | specEnd = append(specEnd, "-m", "multiport", "--sports", r.URL.Query().Get("sports")) 21 | } 22 | if r.URL.Query().Get("dports") != "" { 23 | specEnd = append(specEnd, "-m", "multiport", "--dports", r.URL.Query().Get("dports")) 24 | } 25 | if r.URL.Query().Get("notrack") != "" { 26 | specEnd = append(specEnd, "--notrack") 27 | } 28 | if (r.URL.Query().Get("tcpflag1") != "") && (r.URL.Query().Get("tcpflag2") != "") && (vars["proto"] == tcpStr) { 29 | tcpflag := []string{"--tcp-flags", r.URL.Query().Get("tcpflag1"), r.URL.Query().Get("tcpflag2")} 30 | specEnd = append(specEnd, tcpflag...) 31 | } 32 | if r.URL.Query().Get("tcpmss") != "" { 33 | specEnd = append(specEnd, "-m", "tcpmss", "--mss", r.URL.Query().Get("tcpmss")) 34 | } 35 | if vars["iface_in"] != "*" { 36 | specEnd = append(specEnd, "-i", vars["iface_in"]) 37 | } 38 | if vars["iface_out"] != "*" { 39 | specEnd = append(specEnd, "-o", vars["iface_out"]) 40 | } 41 | srcRange := strings.Contains(vars["source"], "-") 42 | dstRange := strings.Contains(vars["destination"], "-") 43 | ruleSpecs := []string{"-p", vars["proto"]} 44 | if srcRange { 45 | ruleSpecs = append(ruleSpecs, "-m", "iprange", "--src-range", strings.ReplaceAll(vars["source"], "_32", "")) 46 | } else { 47 | ruleSpecs = append(ruleSpecs, "-s", strings.ReplaceAll(vars["source"], "_", "/")) 48 | } 49 | if dstRange { 50 | ruleSpecs = append(ruleSpecs, "-m", "iprange", "--dst-range", strings.ReplaceAll(vars["destination"], "_32", "")) 51 | } else { 52 | ruleSpecs = append(ruleSpecs, "-d", strings.ReplaceAll(vars["destination"], "_", "/")) 53 | } 54 | ruleSpecs = append(ruleSpecs, "-j", vars["action"]) 55 | if (r.URL.Query().Get("log-prefix") != "") && vars["action"] == logAct { 56 | ruleSpecs = append(ruleSpecs, "--log-prefix", r.URL.Query().Get("log-prefix")) 57 | } 58 | ruleSpecs = append(ruleSpecs, specEnd...) 59 | 60 | return ruleSpecs 61 | } 62 | 63 | func checkPosRaw(r *http.Request) ([]string, error) { 64 | vars := mux.Vars(r) 65 | var linenumber []string 66 | 67 | line := []string{vars["action"], vars["proto"], "--"} 68 | line = append(line, vars["iface_in"], vars["iface_out"]) 69 | 70 | srcRange := strings.Contains(vars["source"], "-") 71 | if srcRange { 72 | line = append(line, "0.0.0.0/0") 73 | } else { 74 | source32 := strings.Contains(vars["source"], "_32") 75 | if source32 { 76 | line = append(line, strings.ReplaceAll(vars["source"], "_32", "")) 77 | } else { 78 | line = append(line, strings.ReplaceAll(vars["source"], "_", "/")) 79 | } 80 | } 81 | 82 | dstRange := strings.Contains(vars["destination"], "-") 83 | if dstRange { 84 | line = append(line, "0.0.0.0/0") 85 | } else { 86 | destination32 := strings.Contains(vars["destination"], "_32") 87 | if destination32 { 88 | line = append(line, strings.ReplaceAll(vars["destination"], "_32", "")) 89 | } else { 90 | line = append(line, strings.ReplaceAll(vars["destination"], "_", "/")) 91 | } 92 | } 93 | if srcRange { 94 | line = append(line, "source", "IP", "range", strings.ReplaceAll(vars["source"], "_32", "")) 95 | } 96 | if dstRange { 97 | line = append(line, "destination", "IP", "range", strings.ReplaceAll(vars["destination"], "_32", "")) 98 | } 99 | if r.URL.Query().Get("sports") != "" { 100 | line = append(line, "multiport", "sports", r.URL.Query().Get("sports")) 101 | } 102 | if r.URL.Query().Get("dports") != "" { 103 | line = append(line, "multiport", "dports", r.URL.Query().Get("dports")) 104 | } 105 | if (r.URL.Query().Get("tcpflag1") != "") && (r.URL.Query().Get("tcpflag2") != "") && (vars["proto"] == tcpStr) { 106 | line = append(line, tcpStr) 107 | flags := "" 108 | if r.URL.Query().Get("tcpflag1") == SYNStr { 109 | flags = "flags:0x02/" 110 | } 111 | if (r.URL.Query().Get("tcpflag1") == defaultFlagsMask) || (r.URL.Query().Get("tcpflag1") == defaultFlagsMask2) { 112 | flags = "flags:0x17/" 113 | } 114 | if r.URL.Query().Get("tcpflag2") == SYNStr { 115 | flags = strings.Join([]string{flags, "0x02"}, "") 116 | } 117 | line = append(line, flags) 118 | } 119 | if r.URL.Query().Get("tcpmss") != "" { 120 | line = append(line, "tcpmss", "match", r.URL.Query().Get("tcpmss")) 121 | } 122 | if (r.URL.Query().Get("log-prefix") != "") && vars["action"] == logAct { 123 | line = append(line, "LOG", "flags", "0", "level", "4", "prefix", strings.Join([]string{"\"", r.URL.Query().Get("log-prefix"), "\""}, "")) 124 | } 125 | ipt, err := iptables.New() 126 | if err != nil { 127 | return nil, err 128 | } 129 | args := []string{"-t", "raw", "-vnL", vars["chain"], "--line-numbers"} 130 | if ipt.HasWait { 131 | args = append(args, "--wait") 132 | } 133 | raws, err := ipt.ExecuteList(args) 134 | if err != nil { 135 | return nil, err 136 | } 137 | for i := 0; i < len(raws); i++ { 138 | rawsSlice := strings.Fields(raws[i]) 139 | rawsSliceNoVerb := rawsSlice[3:] 140 | if reflect.DeepEqual(line, rawsSliceNoVerb) { 141 | linenumber = append(linenumber, rawsSlice[0]) 142 | } 143 | } 144 | 145 | return linenumber, nil 146 | } 147 | 148 | // PUT /raw/{action}/{chain}/{proto}/{iface_in}/{iface_out}/{source}/{destination}/?sports=00&dports=00&tcpflag1=XYZ&tcpflag2=Y¬rack=true 149 | func addRaw(w http.ResponseWriter, r *http.Request) { 150 | if *htpasswdfile != "" { 151 | htpasswd := auth.HtpasswdFileProvider(*htpasswdfile) 152 | authenticator := auth.NewBasicAuthenticator("Basic Realm", htpasswd) 153 | usercheck := authenticator.CheckAuth(r) 154 | if usercheck == "" { 155 | w.WriteHeader(http.StatusUnauthorized) 156 | 157 | return 158 | } 159 | } 160 | vars := mux.Vars(r) 161 | ipt, err := iptables.New() 162 | if err != nil { 163 | http.Error(w, err.Error(), 500) 164 | 165 | return 166 | } 167 | rulespecs := rawGenerate(r) 168 | if ipt.HasWait { 169 | rulespecs = append(rulespecs, "--wait") 170 | } 171 | if r.URL.Query().Get("position") != "" { 172 | position, err := strconv.Atoi(r.URL.Query().Get("position")) 173 | if err != nil { 174 | http.Error(w, err.Error(), 500) 175 | 176 | return 177 | } 178 | respErr = ipt.Insert("raw", vars["chain"], position, rulespecs...) 179 | } else { 180 | respErr = ipt.Append("raw", vars["chain"], rulespecs...) 181 | } 182 | if respErr != nil { 183 | w.WriteHeader(http.StatusBadRequest) 184 | fmt.Fprintln(w, respErr) 185 | } 186 | } 187 | 188 | // DELTE /raw/{action}/{chain}/{proto}/{iface_in}/{iface_out}/{source}/{destination}/?sports=00&dports=00&tcpflag1=XYZ&tcpflag2=Y¬rack=true 189 | func delRaw(w http.ResponseWriter, r *http.Request) { 190 | if *htpasswdfile != "" { 191 | htpasswd := auth.HtpasswdFileProvider(*htpasswdfile) 192 | authenticator := auth.NewBasicAuthenticator("Basic Realm", htpasswd) 193 | usercheck := authenticator.CheckAuth(r) 194 | if usercheck == "" { 195 | w.WriteHeader(http.StatusUnauthorized) 196 | 197 | return 198 | } 199 | } 200 | vars := mux.Vars(r) 201 | ipt, err := iptables.New() 202 | if err != nil { 203 | http.Error(w, err.Error(), 500) 204 | 205 | return 206 | } 207 | rulespecs := rawGenerate(r) 208 | if ipt.HasWait { 209 | rulespecs = append(rulespecs, "--wait") 210 | } 211 | respErr = ipt.Delete("raw", vars["chain"], rulespecs...) 212 | if respErr != nil { 213 | w.WriteHeader(http.StatusBadRequest) 214 | fmt.Fprintln(w, respErr) 215 | } 216 | } 217 | 218 | // GET /raw/{action}/{chain}/{proto}/{iface_in}/{iface_out}/{source}/{destination}/?sports=00&dports=00&tcpflag1=XYZ&tcpflag2=Y¬rack=true 219 | func checkRaw(w http.ResponseWriter, r *http.Request) { 220 | if *htpasswdfile != "" { 221 | htpasswd := auth.HtpasswdFileProvider(*htpasswdfile) 222 | authenticator := auth.NewBasicAuthenticator("Basic Realm", htpasswd) 223 | usercheck := authenticator.CheckAuth(r) 224 | if usercheck == "" { 225 | w.WriteHeader(http.StatusUnauthorized) 226 | 227 | return 228 | } 229 | } 230 | ipt, err := iptables.New() 231 | if err != nil { 232 | http.Error(w, err.Error(), 500) 233 | 234 | return 235 | } 236 | rulespecs := rawGenerate(r) 237 | if ipt.HasWait { 238 | rulespecs = append(rulespecs, "--wait") 239 | } 240 | if r.URL.Query().Get("position") != "" { 241 | if r.URL.Query().Get("tcpflag1") != "" { 242 | if (r.URL.Query().Get("tcpflag1") != defaultFlagsMask) && (r.URL.Query().Get("tcpflag1") != SYNStr) && (r.URL.Query().Get("tcpflag1") != defaultFlagsMask2) { 243 | w.WriteHeader(http.StatusBadRequest) 244 | fmt.Fprintln(w, "tcpflag", r.URL.Query().Get("tcpflag1"), "and position not compatible") 245 | 246 | return 247 | } 248 | } 249 | if r.URL.Query().Get("tcpflag2") != "" { 250 | if r.URL.Query().Get("tcpflag2") != SYNStr { 251 | w.WriteHeader(http.StatusBadRequest) 252 | fmt.Fprintln(w, "tcpflag", r.URL.Query().Get("tcpflag2"), "and position not compatible") 253 | 254 | return 255 | } 256 | } 257 | posRaw, err := checkPosRaw(r) 258 | if err != nil { 259 | http.Error(w, err.Error(), 500) 260 | 261 | return 262 | } 263 | switch { 264 | case len(posRaw) == 0: 265 | w.WriteHeader(http.StatusNotFound) 266 | 267 | return 268 | case len(posRaw) != 1: 269 | w.WriteHeader(http.StatusConflict) 270 | 271 | return 272 | case posRaw[0] == r.URL.Query().Get("position"): 273 | return 274 | default: 275 | w.WriteHeader(http.StatusNotFound) 276 | 277 | return 278 | } 279 | } else { 280 | vars := mux.Vars(r) 281 | respStr, respErr := ipt.Exists("raw", vars["chain"], rulespecs...) 282 | if respErr != nil { 283 | w.WriteHeader(http.StatusBadRequest) 284 | fmt.Fprintln(w, respErr) 285 | 286 | return 287 | } 288 | if !respStr { 289 | w.WriteHeader(http.StatusNotFound) 290 | 291 | return 292 | } 293 | } 294 | } 295 | -------------------------------------------------------------------------------- /raw_v6.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "reflect" 7 | "strconv" 8 | "strings" 9 | 10 | auth "github.com/abbot/go-http-auth" 11 | "github.com/gorilla/mux" 12 | "github.com/jeremmfr/go-iptables/iptables" 13 | ) 14 | 15 | func rawGenerateV6(r *http.Request) []string { 16 | vars := mux.Vars(r) 17 | var specEnd []string 18 | 19 | if r.URL.Query().Get("sports") != "" { 20 | specEnd = append(specEnd, "-m", "multiport", "--sports", r.URL.Query().Get("sports")) 21 | } 22 | if r.URL.Query().Get("dports") != "" { 23 | specEnd = append(specEnd, "-m", "multiport", "--dports", r.URL.Query().Get("dports")) 24 | } 25 | if r.URL.Query().Get("notrack") != "" { 26 | specEnd = append(specEnd, "--notrack") 27 | } 28 | if (r.URL.Query().Get("tcpflag1") != "") && (r.URL.Query().Get("tcpflag2") != "") && (vars["proto"] == tcpStr) { 29 | tcpflag := []string{"--tcp-flags", r.URL.Query().Get("tcpflag1"), r.URL.Query().Get("tcpflag2")} 30 | specEnd = append(specEnd, tcpflag...) 31 | } 32 | if r.URL.Query().Get("tcpmss") != "" { 33 | specEnd = append(specEnd, "-m", "tcpmss", "--mss", r.URL.Query().Get("tcpmss")) 34 | } 35 | if vars["iface_in"] != "*" { 36 | specEnd = append(specEnd, "-i", vars["iface_in"]) 37 | } 38 | if vars["iface_out"] != "*" { 39 | specEnd = append(specEnd, "-o", vars["iface_out"]) 40 | } 41 | srcRange := strings.Contains(vars["source"], "-") 42 | dstRange := strings.Contains(vars["destination"], "-") 43 | ruleSpecs := []string{"-p", vars["proto"]} 44 | if srcRange { 45 | ruleSpecs = append(ruleSpecs, "-m", "iprange", "--src-range", strings.ReplaceAll(vars["source"], "_128", "")) 46 | } else { 47 | ruleSpecs = append(ruleSpecs, "-s", strings.ReplaceAll(vars["source"], "_", "/")) 48 | } 49 | if dstRange { 50 | ruleSpecs = append(ruleSpecs, "-m", "iprange", "--dst-range", strings.ReplaceAll(vars["destination"], "_128", "")) 51 | } else { 52 | ruleSpecs = append(ruleSpecs, "-d", strings.ReplaceAll(vars["destination"], "_", "/")) 53 | } 54 | ruleSpecs = append(ruleSpecs, "-j", vars["action"]) 55 | if (r.URL.Query().Get("log-prefix") != "") && vars["action"] == logAct { 56 | ruleSpecs = append(ruleSpecs, "--log-prefix", r.URL.Query().Get("log-prefix")) 57 | } 58 | ruleSpecs = append(ruleSpecs, specEnd...) 59 | 60 | return ruleSpecs 61 | } 62 | 63 | func checkPosRawV6(r *http.Request) ([]string, error) { 64 | vars := mux.Vars(r) 65 | var linenumber []string 66 | 67 | line := []string{vars["action"], vars["proto"]} 68 | line = append(line, vars["iface_in"], vars["iface_out"]) 69 | 70 | srcRange := strings.Contains(vars["source"], "-") 71 | if srcRange { 72 | line = append(line, "::/0") 73 | } else { 74 | source128 := strings.Contains(vars["source"], "_128") 75 | if source128 { 76 | line = append(line, strings.ReplaceAll(vars["source"], "_128", "")) 77 | } else { 78 | line = append(line, strings.ReplaceAll(vars["source"], "_", "/")) 79 | } 80 | } 81 | 82 | dstRange := strings.Contains(vars["destination"], "-") 83 | if dstRange { 84 | line = append(line, "::/0") 85 | } else { 86 | destination128 := strings.Contains(vars["destination"], "_128") 87 | if destination128 { 88 | line = append(line, strings.ReplaceAll(vars["destination"], "_128", "")) 89 | } else { 90 | line = append(line, strings.ReplaceAll(vars["destination"], "_", "/")) 91 | } 92 | } 93 | if srcRange { 94 | line = append(line, "source", "IP", "range", strings.ReplaceAll(vars["source"], "_128", "")) 95 | } 96 | if dstRange { 97 | line = append(line, "destination", "IP", "range", strings.ReplaceAll(vars["destination"], "_128", "")) 98 | } 99 | if r.URL.Query().Get("sports") != "" { 100 | line = append(line, "multiport", "sports", r.URL.Query().Get("sports")) 101 | } 102 | if r.URL.Query().Get("dports") != "" { 103 | line = append(line, "multiport", "dports", r.URL.Query().Get("dports")) 104 | } 105 | if (r.URL.Query().Get("tcpflag1") != "") && (r.URL.Query().Get("tcpflag2") != "") && (vars["proto"] == tcpStr) { 106 | line = append(line, tcpStr) 107 | flags := "" 108 | if r.URL.Query().Get("tcpflag1") == SYNStr { 109 | flags = "flags:0x02/" 110 | } 111 | if (r.URL.Query().Get("tcpflag1") == defaultFlagsMask) || (r.URL.Query().Get("tcpflag1") == defaultFlagsMask2) { 112 | flags = "flags:0x17/" 113 | } 114 | if r.URL.Query().Get("tcpflag2") == SYNStr { 115 | flags = strings.Join([]string{flags, "0x02"}, "") 116 | } 117 | line = append(line, flags) 118 | } 119 | if r.URL.Query().Get("tcpmss") != "" { 120 | line = append(line, "tcpmss", "match", r.URL.Query().Get("tcpmss")) 121 | } 122 | if (r.URL.Query().Get("log-prefix") != "") && vars["action"] == logAct { 123 | line = append(line, "LOG", "flags", "0", "level", "4", "prefix", strings.Join([]string{"\"", r.URL.Query().Get("log-prefix"), "\""}, "")) 124 | } 125 | ipt, err := iptables.NewWithProtocol(v6) 126 | if err != nil { 127 | return nil, err 128 | } 129 | args := []string{"-t", "raw", "-vnL", vars["chain"], "--line-numbers"} 130 | if ipt.HasWait { 131 | args = append(args, "--wait") 132 | } 133 | raws, err := ipt.ExecuteList(args) 134 | if err != nil { 135 | return nil, err 136 | } 137 | for i := 0; i < len(raws); i++ { 138 | rawsSlice := strings.Fields(raws[i]) 139 | rawsSliceNoVerb := rawsSlice[3:] 140 | if reflect.DeepEqual(line, rawsSliceNoVerb) { 141 | linenumber = append(linenumber, rawsSlice[0]) 142 | } 143 | } 144 | 145 | return linenumber, nil 146 | } 147 | 148 | // PUT /raw_v6/{action}/{chain}/{proto}/{iface_in}/{iface_out}/{source}/{destination}/?sports=00&dports=00&tcpflag1=XYZ&tcpflag2=Y¬rack=true 149 | func addRawV6(w http.ResponseWriter, r *http.Request) { 150 | if *htpasswdfile != "" { 151 | htpasswd := auth.HtpasswdFileProvider(*htpasswdfile) 152 | authenticator := auth.NewBasicAuthenticator("Basic Realm", htpasswd) 153 | usercheck := authenticator.CheckAuth(r) 154 | if usercheck == "" { 155 | w.WriteHeader(http.StatusUnauthorized) 156 | 157 | return 158 | } 159 | } 160 | vars := mux.Vars(r) 161 | ipt, err := iptables.NewWithProtocol(v6) 162 | if err != nil { 163 | http.Error(w, err.Error(), 500) 164 | 165 | return 166 | } 167 | rulespecs := rawGenerateV6(r) 168 | if ipt.HasWait { 169 | rulespecs = append(rulespecs, "--wait") 170 | } 171 | if r.URL.Query().Get("position") != "" { 172 | position, err := strconv.Atoi(r.URL.Query().Get("position")) 173 | if err != nil { 174 | http.Error(w, err.Error(), 500) 175 | 176 | return 177 | } 178 | respErr = ipt.Insert("raw", vars["chain"], position, rulespecs...) 179 | } else { 180 | respErr = ipt.Append("raw", vars["chain"], rulespecs...) 181 | } 182 | if respErr != nil { 183 | w.WriteHeader(http.StatusBadRequest) 184 | fmt.Fprintln(w, respErr) 185 | } 186 | } 187 | 188 | // DELTE /raw_v6/{action}/{chain}/{proto}/{iface_in}/{iface_out}/{source}/{destination}/?sports=00&dports=00&tcpflag1=XYZ&tcpflag2=Y¬rack=true 189 | func delRawV6(w http.ResponseWriter, r *http.Request) { 190 | if *htpasswdfile != "" { 191 | htpasswd := auth.HtpasswdFileProvider(*htpasswdfile) 192 | authenticator := auth.NewBasicAuthenticator("Basic Realm", htpasswd) 193 | usercheck := authenticator.CheckAuth(r) 194 | if usercheck == "" { 195 | w.WriteHeader(http.StatusUnauthorized) 196 | 197 | return 198 | } 199 | } 200 | vars := mux.Vars(r) 201 | ipt, err := iptables.NewWithProtocol(v6) 202 | if err != nil { 203 | http.Error(w, err.Error(), 500) 204 | 205 | return 206 | } 207 | rulespecs := rawGenerateV6(r) 208 | if ipt.HasWait { 209 | rulespecs = append(rulespecs, "--wait") 210 | } 211 | respErr = ipt.Delete("raw", vars["chain"], rulespecs...) 212 | if respErr != nil { 213 | w.WriteHeader(http.StatusBadRequest) 214 | fmt.Fprintln(w, respErr) 215 | } 216 | } 217 | 218 | // GET /raw_v6/{action}/{chain}/{proto}/{iface_in}/{iface_out}/{source}/{destination}/?sports=00&dports=00&tcpflag1=XYZ&tcpflag2=Y¬rack=true 219 | func checkRawV6(w http.ResponseWriter, r *http.Request) { 220 | if *htpasswdfile != "" { 221 | htpasswd := auth.HtpasswdFileProvider(*htpasswdfile) 222 | authenticator := auth.NewBasicAuthenticator("Basic Realm", htpasswd) 223 | usercheck := authenticator.CheckAuth(r) 224 | if usercheck == "" { 225 | w.WriteHeader(http.StatusUnauthorized) 226 | 227 | return 228 | } 229 | } 230 | ipt, err := iptables.NewWithProtocol(v6) 231 | if err != nil { 232 | http.Error(w, err.Error(), 500) 233 | 234 | return 235 | } 236 | rulespecs := rawGenerateV6(r) 237 | if ipt.HasWait { 238 | rulespecs = append(rulespecs, "--wait") 239 | } 240 | if r.URL.Query().Get("position") != "" { 241 | if r.URL.Query().Get("tcpflag1") != "" { 242 | if (r.URL.Query().Get("tcpflag1") != defaultFlagsMask) && (r.URL.Query().Get("tcpflag1") != SYNStr) && (r.URL.Query().Get("tcpflag1") != defaultFlagsMask2) { 243 | w.WriteHeader(http.StatusBadRequest) 244 | fmt.Fprintln(w, "tcpflag", r.URL.Query().Get("tcpflag1"), "and position not compatible") 245 | 246 | return 247 | } 248 | } 249 | if r.URL.Query().Get("tcpflag2") != "" { 250 | if r.URL.Query().Get("tcpflag2") != SYNStr { 251 | w.WriteHeader(http.StatusBadRequest) 252 | fmt.Fprintln(w, "tcpflag", r.URL.Query().Get("tcpflag2"), "and position not compatible") 253 | 254 | return 255 | } 256 | } 257 | posRaw, err := checkPosRawV6(r) 258 | if err != nil { 259 | http.Error(w, err.Error(), 500) 260 | 261 | return 262 | } 263 | switch { 264 | case len(posRaw) == 0: 265 | w.WriteHeader(http.StatusNotFound) 266 | 267 | return 268 | case len(posRaw) != 1: 269 | w.WriteHeader(http.StatusConflict) 270 | 271 | return 272 | case posRaw[0] == r.URL.Query().Get("position"): 273 | return 274 | default: 275 | w.WriteHeader(http.StatusNotFound) 276 | 277 | return 278 | } 279 | } else { 280 | vars := mux.Vars(r) 281 | respStr, respErr := ipt.Exists("raw", vars["chain"], rulespecs...) 282 | if respErr != nil { 283 | w.WriteHeader(http.StatusBadRequest) 284 | fmt.Fprintln(w, respErr) 285 | 286 | return 287 | } 288 | if !respStr { 289 | w.WriteHeader(http.StatusNotFound) 290 | 291 | return 292 | } 293 | } 294 | } 295 | -------------------------------------------------------------------------------- /nat.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "reflect" 7 | "strconv" 8 | "strings" 9 | 10 | auth "github.com/abbot/go-http-auth" 11 | "github.com/gorilla/mux" 12 | "github.com/jeremmfr/go-iptables/iptables" 13 | ) 14 | 15 | func dnatGenerate(r *http.Request) []string { 16 | vars := mux.Vars(r) 17 | rulespecs := []string{"-p", vars["proto"], "-i", vars["iface"]} 18 | if r.URL.Query().Get("except") == trueStr { 19 | rulespecs = append(rulespecs, "!") 20 | } 21 | srcRange := strings.Contains(vars["source"], "-") 22 | dstRange := strings.Contains(vars["destination"], "-") 23 | if srcRange { 24 | rulespecs = append(rulespecs, "-m", "iprange", "--src-range", strings.ReplaceAll(vars["source"], "_32", "")) 25 | } else { 26 | rulespecs = append(rulespecs, "-s", strings.ReplaceAll(vars["source"], "_", "/")) 27 | } 28 | if dstRange { 29 | rulespecs = append(rulespecs, "-m", "iprange", "--dst-range", strings.ReplaceAll(vars["destination"], "_32", "")) 30 | } else { 31 | rulespecs = append(rulespecs, "-d", strings.ReplaceAll(vars["destination"], "_", "/")) 32 | } 33 | rulespecs = append(rulespecs, "-j", "DNAT", "--to-destination", vars["nat_final"]) 34 | if r.URL.Query().Get("dport") != "" { 35 | rulespecs = append(rulespecs, "--dport", r.URL.Query().Get("dport")) 36 | } 37 | if r.URL.Query().Get("nth_every") != "" { 38 | rulespecs = append(rulespecs, "-m", "statistic", "--mode", "nth", "--every", r.URL.Query().Get("nth_every"), "--packet", r.URL.Query().Get("nth_packet")) 39 | } 40 | 41 | return rulespecs 42 | } 43 | 44 | func snatGenerate(r *http.Request) []string { 45 | vars := mux.Vars(r) 46 | rulespecs := []string{"-p", vars["proto"], "-o", vars["iface"]} 47 | srcRange := strings.Contains(vars["source"], "-") 48 | dstRange := strings.Contains(vars["destination"], "-") 49 | if srcRange { 50 | rulespecs = append(rulespecs, "-m", "iprange", "--src-range", strings.ReplaceAll(vars["source"], "_32", "")) 51 | } else { 52 | rulespecs = append(rulespecs, "-s", strings.ReplaceAll(vars["source"], "_", "/")) 53 | } 54 | if r.URL.Query().Get("except") == trueStr { 55 | rulespecs = append(rulespecs, "!") 56 | } 57 | if dstRange { 58 | rulespecs = append(rulespecs, "-m", "iprange", "--dst-range", strings.ReplaceAll(vars["destination"], "_32", "")) 59 | } else { 60 | rulespecs = append(rulespecs, "-d", strings.ReplaceAll(vars["destination"], "_", "/")) 61 | } 62 | rulespecs = append(rulespecs, "-j", "SNAT", "--to-source", vars["nat_final"]) 63 | if r.URL.Query().Get("dport") != "" { 64 | rulespecs = append(rulespecs, "--dport", r.URL.Query().Get("dport")) 65 | } 66 | if r.URL.Query().Get("nth_every") != "" { 67 | rulespecs = append(rulespecs, "-m", "statistic", "--mode", "nth", "--every", r.URL.Query().Get("nth_every"), "--packet", r.URL.Query().Get("nth_packet")) 68 | } 69 | 70 | return rulespecs 71 | } 72 | 73 | func CheckPosNat(r *http.Request) ([]string, error) { 74 | vars := mux.Vars(r) 75 | var linenumber []string 76 | var line []string 77 | 78 | if vars["action"] == dnatAct { 79 | line = append(line, "DNAT", vars["proto"], "--", vars["iface"], "*") 80 | } 81 | if vars["action"] == snatAct { 82 | line = append(line, "SNAT", vars["proto"], "--", "*", vars["iface"]) 83 | } 84 | source32 := strings.Contains(vars["source"], "_32") 85 | destination32 := strings.Contains(vars["destination"], "_32") 86 | 87 | if source32 { 88 | if (vars["action"] == dnatAct) && (r.URL.Query().Get("except") == trueStr) { 89 | line = append(line, strings.Join([]string{"!", strings.ReplaceAll(vars["source"], "_32", "")}, "")) 90 | } else { 91 | line = append(line, strings.ReplaceAll(vars["source"], "_32", "")) 92 | } 93 | } else { 94 | if (vars["action"] == dnatAct) && (r.URL.Query().Get("except") == trueStr) { 95 | line = append(line, strings.Join([]string{"!", strings.ReplaceAll(vars["source"], "_", "/")}, "")) 96 | } else { 97 | line = append(line, strings.ReplaceAll(vars["source"], "_", "/")) 98 | } 99 | } 100 | if destination32 { 101 | if (vars["action"] == snatAct) && (r.URL.Query().Get("except") == trueStr) { 102 | line = append(line, strings.Join([]string{"!", strings.ReplaceAll(vars["destination"], "_32", "")}, "")) 103 | } else { 104 | line = append(line, strings.ReplaceAll(vars["destination"], "_32", "")) 105 | } 106 | } else { 107 | if (vars["action"] == snatAct) && (r.URL.Query().Get("except") == trueStr) { 108 | line = append(line, strings.Join([]string{"!", strings.ReplaceAll(vars["destination"], "_", "/")}, "")) 109 | } else { 110 | line = append(line, strings.ReplaceAll(vars["destination"], "_", "/")) 111 | } 112 | } 113 | if r.URL.Query().Get("dport") != "" { 114 | line = append(line, "tcp", strings.Join([]string{"dpt:", r.URL.Query().Get("dport")}, "")) 115 | } 116 | if r.URL.Query().Get("nth_every") != "" { 117 | if r.URL.Query().Get("nth_packet") == "0" { 118 | line = append(line, "statistic", "mode", "nth", "every", r.URL.Query().Get("nth_every")) 119 | } else { 120 | line = append(line, "statistic", "mode", "nth", "every", r.URL.Query().Get("nth_every"), "packet", r.URL.Query().Get("nth_packet")) 121 | } 122 | } 123 | line = append(line, strings.Join([]string{"to:", vars["nat_final"]}, "")) 124 | 125 | ipt, err := iptables.New() 126 | if err != nil { 127 | return nil, err 128 | } 129 | args := []string{"-t", "nat", "-vnL", vars["chain"], "--line-numbers"} 130 | if ipt.HasWait { 131 | args = append(args, "--wait") 132 | } 133 | nats, err := ipt.ExecuteList(args) 134 | if err != nil { 135 | return nil, err 136 | } 137 | for i := 0; i < len(nats); i++ { 138 | natsSlice := strings.Fields(nats[i]) 139 | natsSliceNoVerb := natsSlice[3:] 140 | if reflect.DeepEqual(line, natsSliceNoVerb) { 141 | linenumber = append(linenumber, natsSlice[0]) 142 | } 143 | } 144 | 145 | return linenumber, nil 146 | } 147 | 148 | // PUT /nat/{action}/{chain}/{proto}/{iface}/{source}/{destination}/{nat_final}/?dport=00 149 | func addNat(w http.ResponseWriter, r *http.Request) { 150 | if *htpasswdfile != "" { 151 | htpasswd := auth.HtpasswdFileProvider(*htpasswdfile) 152 | authenticator := auth.NewBasicAuthenticator("Basic Realm", htpasswd) 153 | usercheck := authenticator.CheckAuth(r) 154 | if usercheck == "" { 155 | w.WriteHeader(http.StatusUnauthorized) 156 | 157 | return 158 | } 159 | } 160 | vars := mux.Vars(r) 161 | ipt, err := iptables.New() 162 | if err != nil { 163 | http.Error(w, err.Error(), 500) 164 | 165 | return 166 | } 167 | var rulespecs []string 168 | if (r.URL.Query().Get("nth_every") != "") || (r.URL.Query().Get("nth_packet") != "") { 169 | if r.URL.Query().Get("nth_every") == "" { 170 | w.WriteHeader(http.StatusBadRequest) 171 | fmt.Fprintln(w, "Missing nth every") 172 | 173 | return 174 | } 175 | if r.URL.Query().Get("nth_packet") == "" { 176 | w.WriteHeader(http.StatusBadRequest) 177 | fmt.Fprintln(w, "Missing nth packet") 178 | 179 | return 180 | } 181 | } 182 | switch vars["action"] { 183 | case dnatAct: 184 | rulespecs = dnatGenerate(r) 185 | case snatAct: 186 | rulespecs = snatGenerate(r) 187 | default: 188 | w.WriteHeader(http.StatusNotFound) 189 | 190 | return 191 | } 192 | if ipt.HasWait { 193 | rulespecs = append(rulespecs, "--wait") 194 | } 195 | if r.URL.Query().Get("position") != "" { 196 | position, err := strconv.Atoi(r.URL.Query().Get("position")) 197 | if err != nil { 198 | http.Error(w, err.Error(), 500) 199 | 200 | return 201 | } 202 | respErr = ipt.Insert("nat", vars["chain"], position, rulespecs...) 203 | } else { 204 | respErr = ipt.Append("nat", vars["chain"], rulespecs...) 205 | } 206 | if respErr != nil { 207 | w.WriteHeader(http.StatusBadRequest) 208 | fmt.Fprintln(w, respErr) 209 | } 210 | } 211 | 212 | // DELETE /nat/{action}/{chain}/{proto}/{iface}/{source}/{destination}/{nat_final}/?dport=00 213 | func delNat(w http.ResponseWriter, r *http.Request) { 214 | if *htpasswdfile != "" { 215 | htpasswd := auth.HtpasswdFileProvider(*htpasswdfile) 216 | authenticator := auth.NewBasicAuthenticator("Basic Realm", htpasswd) 217 | usercheck := authenticator.CheckAuth(r) 218 | if usercheck == "" { 219 | w.WriteHeader(http.StatusUnauthorized) 220 | 221 | return 222 | } 223 | } 224 | vars := mux.Vars(r) 225 | 226 | ipt, err := iptables.New() 227 | if err != nil { 228 | http.Error(w, err.Error(), 500) 229 | 230 | return 231 | } 232 | var rulespecs []string 233 | if (r.URL.Query().Get("nth_every") != "") || (r.URL.Query().Get("nth_packet") != "") { 234 | if r.URL.Query().Get("nth_every") == "" { 235 | w.WriteHeader(http.StatusBadRequest) 236 | fmt.Fprintln(w, "Missing nth every") 237 | 238 | return 239 | } 240 | if r.URL.Query().Get("nth_packet") == "" { 241 | w.WriteHeader(http.StatusBadRequest) 242 | fmt.Fprintln(w, "Missing nth packet") 243 | 244 | return 245 | } 246 | } 247 | switch vars["action"] { 248 | case dnatAct: 249 | rulespecs = dnatGenerate(r) 250 | case snatAct: 251 | rulespecs = snatGenerate(r) 252 | default: 253 | w.WriteHeader(http.StatusNotFound) 254 | 255 | return 256 | } 257 | if ipt.HasWait { 258 | rulespecs = append(rulespecs, "--wait") 259 | } 260 | respErr = ipt.Delete("nat", vars["chain"], rulespecs...) 261 | if respErr != nil { 262 | w.WriteHeader(http.StatusBadRequest) 263 | fmt.Fprintln(w, respErr) 264 | } 265 | } 266 | 267 | // GET /nat/{action}/{chain}/{proto}/{iface}/{source}/{destination}/{nat_final}/?dport=00 268 | func checkNat(w http.ResponseWriter, r *http.Request) { 269 | if *htpasswdfile != "" { 270 | htpasswd := auth.HtpasswdFileProvider(*htpasswdfile) 271 | authenticator := auth.NewBasicAuthenticator("Basic Realm", htpasswd) 272 | usercheck := authenticator.CheckAuth(r) 273 | if usercheck == "" { 274 | w.WriteHeader(http.StatusUnauthorized) 275 | 276 | return 277 | } 278 | } 279 | vars := mux.Vars(r) 280 | 281 | ipt, err := iptables.New() 282 | if err != nil { 283 | http.Error(w, err.Error(), 500) 284 | 285 | return 286 | } 287 | if r.URL.Query().Get("position") != "" { 288 | posNat, err := CheckPosNat(r) 289 | if err != nil { 290 | http.Error(w, err.Error(), 500) 291 | 292 | return 293 | } 294 | switch { 295 | case len(posNat) == 0: 296 | w.WriteHeader(http.StatusNotFound) 297 | 298 | return 299 | case len(posNat) != 1: 300 | w.WriteHeader(http.StatusConflict) 301 | 302 | return 303 | case posNat[0] == r.URL.Query().Get("position"): 304 | return 305 | default: 306 | w.WriteHeader(http.StatusNotFound) 307 | 308 | return 309 | } 310 | } 311 | var rulespecs []string 312 | if (r.URL.Query().Get("nth_every") != "") || (r.URL.Query().Get("nth_packet") != "") { 313 | if r.URL.Query().Get("nth_every") == "" { 314 | w.WriteHeader(http.StatusBadRequest) 315 | fmt.Fprintln(w, "Missing nth every") 316 | 317 | return 318 | } 319 | if r.URL.Query().Get("nth_packet") == "" { 320 | w.WriteHeader(http.StatusBadRequest) 321 | fmt.Fprintln(w, "Missing nth packet") 322 | 323 | return 324 | } 325 | } 326 | switch vars["action"] { 327 | case dnatAct: 328 | rulespecs = dnatGenerate(r) 329 | case snatAct: 330 | rulespecs = snatGenerate(r) 331 | default: 332 | w.WriteHeader(http.StatusNotFound) 333 | 334 | return 335 | } 336 | if ipt.HasWait { 337 | rulespecs = append(rulespecs, "--wait") 338 | } 339 | respStr, respErr := ipt.Exists("nat", vars["chain"], rulespecs...) 340 | if respErr != nil { 341 | w.WriteHeader(http.StatusBadRequest) 342 | fmt.Fprintln(w, respErr) 343 | } 344 | if !respStr { 345 | w.WriteHeader(http.StatusNotFound) 346 | } 347 | } 348 | -------------------------------------------------------------------------------- /nat_v6.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "reflect" 7 | "strconv" 8 | "strings" 9 | 10 | auth "github.com/abbot/go-http-auth" 11 | "github.com/gorilla/mux" 12 | "github.com/jeremmfr/go-iptables/iptables" 13 | ) 14 | 15 | func dnatGenerateV6(r *http.Request) []string { 16 | vars := mux.Vars(r) 17 | rulespecs := []string{"-p", vars["proto"], "-i", vars["iface"]} 18 | if r.URL.Query().Get("except") == trueStr { 19 | rulespecs = append(rulespecs, "!") 20 | } 21 | srcRange := strings.Contains(vars["source"], "-") 22 | dstRange := strings.Contains(vars["destination"], "-") 23 | if srcRange { 24 | rulespecs = append(rulespecs, "-m", "iprange", "--src-range", strings.ReplaceAll(vars["source"], "_128", "")) 25 | } else { 26 | rulespecs = append(rulespecs, "-s", strings.ReplaceAll(vars["source"], "_", "/")) 27 | } 28 | if dstRange { 29 | rulespecs = append(rulespecs, "-m", "iprange", "--dst-range", strings.ReplaceAll(vars["destination"], "_128", "")) 30 | } else { 31 | rulespecs = append(rulespecs, "-d", strings.ReplaceAll(vars["destination"], "_", "/")) 32 | } 33 | rulespecs = append(rulespecs, "-j", "DNAT", "--to-destination", vars["nat_final"]) 34 | if r.URL.Query().Get("dport") != "" { 35 | rulespecs = append(rulespecs, "--dport", r.URL.Query().Get("dport")) 36 | } 37 | if r.URL.Query().Get("nth_every") != "" { 38 | rulespecs = append(rulespecs, "-m", "statistic", "--mode", "nth", "--every", r.URL.Query().Get("nth_every"), "--packet", r.URL.Query().Get("nth_packet")) 39 | } 40 | 41 | return rulespecs 42 | } 43 | 44 | func snatGenerateV6(r *http.Request) []string { 45 | vars := mux.Vars(r) 46 | rulespecs := []string{"-p", vars["proto"], "-o", vars["iface"]} 47 | srcRange := strings.Contains(vars["source"], "-") 48 | dstRange := strings.Contains(vars["destination"], "-") 49 | if srcRange { 50 | rulespecs = append(rulespecs, "-m", "iprange", "--src-range", strings.ReplaceAll(vars["source"], "_128", "")) 51 | } else { 52 | rulespecs = append(rulespecs, "-s", strings.ReplaceAll(vars["source"], "_", "/")) 53 | } 54 | if r.URL.Query().Get("except") == trueStr { 55 | rulespecs = append(rulespecs, "!") 56 | } 57 | if dstRange { 58 | rulespecs = append(rulespecs, "-m", "iprange", "--dst-range", strings.ReplaceAll(vars["destination"], "_128", "")) 59 | } else { 60 | rulespecs = append(rulespecs, "-d", strings.ReplaceAll(vars["destination"], "_", "/")) 61 | } 62 | rulespecs = append(rulespecs, "-j", "SNAT", "--to-source", vars["nat_final"]) 63 | if r.URL.Query().Get("dport") != "" { 64 | rulespecs = append(rulespecs, "--dport", r.URL.Query().Get("dport")) 65 | } 66 | if r.URL.Query().Get("nth_every") != "" { 67 | rulespecs = append(rulespecs, "-m", "statistic", "--mode", "nth", "--every", r.URL.Query().Get("nth_every"), "--packet", r.URL.Query().Get("nth_packet")) 68 | } 69 | 70 | return rulespecs 71 | } 72 | 73 | func checkPosNatV6(r *http.Request) ([]string, error) { 74 | vars := mux.Vars(r) 75 | var linenumber []string 76 | var line []string 77 | 78 | if vars["action"] == dnatAct { 79 | line = append(line, "DNAT", vars["proto"], vars["iface"], "*") 80 | } 81 | if vars["action"] == snatAct { 82 | line = append(line, "SNAT", vars["proto"], "*", vars["iface"]) 83 | } 84 | source128 := strings.Contains(vars["source"], "_128") 85 | destination128 := strings.Contains(vars["destination"], "_128") 86 | 87 | if source128 { 88 | if (vars["action"] == dnatAct) && (r.URL.Query().Get("except") == trueStr) { 89 | line = append(line, strings.Join([]string{"!", strings.ReplaceAll(vars["source"], "_128", "")}, "")) 90 | } else { 91 | line = append(line, strings.ReplaceAll(vars["source"], "_128", "")) 92 | } 93 | } else { 94 | if (vars["action"] == dnatAct) && (r.URL.Query().Get("except") == trueStr) { 95 | line = append(line, strings.Join([]string{"!", strings.ReplaceAll(vars["source"], "_", "/")}, "")) 96 | } else { 97 | line = append(line, strings.ReplaceAll(vars["source"], "_", "/")) 98 | } 99 | } 100 | if destination128 { 101 | if (vars["action"] == snatAct) && (r.URL.Query().Get("except") == trueStr) { 102 | line = append(line, strings.Join([]string{"!", strings.ReplaceAll(vars["destination"], "_128", "")}, "")) 103 | } else { 104 | line = append(line, strings.ReplaceAll(vars["destination"], "_128", "")) 105 | } 106 | } else { 107 | if (vars["action"] == snatAct) && (r.URL.Query().Get("except") == trueStr) { 108 | line = append(line, strings.Join([]string{"!", strings.ReplaceAll(vars["destination"], "_", "/")}, "")) 109 | } else { 110 | line = append(line, strings.ReplaceAll(vars["destination"], "_", "/")) 111 | } 112 | } 113 | if r.URL.Query().Get("dport") != "" { 114 | line = append(line, "tcp", strings.Join([]string{"dpt:", r.URL.Query().Get("dport")}, "")) 115 | } 116 | if r.URL.Query().Get("nth_every") != "" { 117 | if r.URL.Query().Get("nth_packet") == "0" { 118 | line = append(line, "statistic", "mode", "nth", "every", r.URL.Query().Get("nth_every")) 119 | } else { 120 | line = append(line, "statistic", "mode", "nth", "every", r.URL.Query().Get("nth_every"), "packet", r.URL.Query().Get("nth_packet")) 121 | } 122 | } 123 | line = append(line, strings.Join([]string{"to:", vars["nat_final"]}, "")) 124 | 125 | ipt, err := iptables.NewWithProtocol(v6) 126 | if err != nil { 127 | return nil, err 128 | } 129 | args := []string{"-t", "nat", "-vnL", vars["chain"], "--line-numbers"} 130 | if ipt.HasWait { 131 | args = append(args, "--wait") 132 | } 133 | nats, err := ipt.ExecuteList(args) 134 | if err != nil { 135 | return nil, err 136 | } 137 | for i := 0; i < len(nats); i++ { 138 | natsSlice := strings.Fields(nats[i]) 139 | natsSliceNoVerb := natsSlice[3:] 140 | if reflect.DeepEqual(line, natsSliceNoVerb) { 141 | linenumber = append(linenumber, natsSlice[0]) 142 | } 143 | } 144 | 145 | return linenumber, nil 146 | } 147 | 148 | // PUT /nat_v6/{action}/{chain}/{proto}/{iface}/{source}/{destination}/{nat_final}/?dport=00 149 | func addNatV6(w http.ResponseWriter, r *http.Request) { 150 | if *htpasswdfile != "" { 151 | htpasswd := auth.HtpasswdFileProvider(*htpasswdfile) 152 | authenticator := auth.NewBasicAuthenticator("Basic Realm", htpasswd) 153 | usercheck := authenticator.CheckAuth(r) 154 | if usercheck == "" { 155 | w.WriteHeader(http.StatusUnauthorized) 156 | 157 | return 158 | } 159 | } 160 | vars := mux.Vars(r) 161 | ipt, err := iptables.NewWithProtocol(v6) 162 | if err != nil { 163 | http.Error(w, err.Error(), 500) 164 | 165 | return 166 | } 167 | var rulespecs []string 168 | if (r.URL.Query().Get("nth_every") != "") || (r.URL.Query().Get("nth_packet") != "") { 169 | if r.URL.Query().Get("nth_every") == "" { 170 | w.WriteHeader(http.StatusBadRequest) 171 | fmt.Fprintln(w, "Missing nth every") 172 | 173 | return 174 | } 175 | if r.URL.Query().Get("nth_packet") == "" { 176 | w.WriteHeader(http.StatusBadRequest) 177 | fmt.Fprintln(w, "Missing nth packet") 178 | 179 | return 180 | } 181 | } 182 | switch vars["action"] { 183 | case dnatAct: 184 | rulespecs = dnatGenerateV6(r) 185 | case snatAct: 186 | rulespecs = snatGenerateV6(r) 187 | default: 188 | w.WriteHeader(http.StatusNotFound) 189 | 190 | return 191 | } 192 | if ipt.HasWait { 193 | rulespecs = append(rulespecs, "--wait") 194 | } 195 | if r.URL.Query().Get("position") != "" { 196 | position, err := strconv.Atoi(r.URL.Query().Get("position")) 197 | if err != nil { 198 | http.Error(w, err.Error(), 500) 199 | 200 | return 201 | } 202 | respErr = ipt.Insert("nat", vars["chain"], position, rulespecs...) 203 | } else { 204 | respErr = ipt.Append("nat", vars["chain"], rulespecs...) 205 | } 206 | if respErr != nil { 207 | w.WriteHeader(http.StatusBadRequest) 208 | fmt.Fprintln(w, respErr) 209 | } 210 | } 211 | 212 | // DELETE /nat_v6/{action}/{chain}/{proto}/{iface}/{source}/{destination}/{nat_final}/?dport=00 213 | func delNatV6(w http.ResponseWriter, r *http.Request) { 214 | if *htpasswdfile != "" { 215 | htpasswd := auth.HtpasswdFileProvider(*htpasswdfile) 216 | authenticator := auth.NewBasicAuthenticator("Basic Realm", htpasswd) 217 | usercheck := authenticator.CheckAuth(r) 218 | if usercheck == "" { 219 | w.WriteHeader(http.StatusUnauthorized) 220 | 221 | return 222 | } 223 | } 224 | vars := mux.Vars(r) 225 | 226 | ipt, err := iptables.NewWithProtocol(v6) 227 | if err != nil { 228 | http.Error(w, err.Error(), 500) 229 | 230 | return 231 | } 232 | var rulespecs []string 233 | if (r.URL.Query().Get("nth_every") != "") || (r.URL.Query().Get("nth_packet") != "") { 234 | if r.URL.Query().Get("nth_every") == "" { 235 | w.WriteHeader(http.StatusBadRequest) 236 | fmt.Fprintln(w, "Missing nth every") 237 | 238 | return 239 | } 240 | if r.URL.Query().Get("nth_packet") == "" { 241 | w.WriteHeader(http.StatusBadRequest) 242 | fmt.Fprintln(w, "Missing nth packet") 243 | 244 | return 245 | } 246 | } 247 | switch vars["action"] { 248 | case dnatAct: 249 | rulespecs = dnatGenerateV6(r) 250 | case snatAct: 251 | rulespecs = snatGenerateV6(r) 252 | default: 253 | w.WriteHeader(http.StatusNotFound) 254 | 255 | return 256 | } 257 | if ipt.HasWait { 258 | rulespecs = append(rulespecs, "--wait") 259 | } 260 | respErr = ipt.Delete("nat", vars["chain"], rulespecs...) 261 | if respErr != nil { 262 | w.WriteHeader(http.StatusBadRequest) 263 | fmt.Fprintln(w, respErr) 264 | } 265 | } 266 | 267 | // GET /nat_v6/{action}/{chain}/{proto}/{iface}/{source}/{destination}/{nat_final}/?dport=00 268 | func checkNatV6(w http.ResponseWriter, r *http.Request) { 269 | if *htpasswdfile != "" { 270 | htpasswd := auth.HtpasswdFileProvider(*htpasswdfile) 271 | authenticator := auth.NewBasicAuthenticator("Basic Realm", htpasswd) 272 | usercheck := authenticator.CheckAuth(r) 273 | if usercheck == "" { 274 | w.WriteHeader(http.StatusUnauthorized) 275 | 276 | return 277 | } 278 | } 279 | vars := mux.Vars(r) 280 | 281 | ipt, err := iptables.NewWithProtocol(v6) 282 | if err != nil { 283 | http.Error(w, err.Error(), 500) 284 | 285 | return 286 | } 287 | if r.URL.Query().Get("position") != "" { 288 | posNat, err := checkPosNatV6(r) 289 | if err != nil { 290 | http.Error(w, err.Error(), 500) 291 | 292 | return 293 | } 294 | switch { 295 | case len(posNat) == 0: 296 | w.WriteHeader(http.StatusNotFound) 297 | 298 | return 299 | case len(posNat) != 1: 300 | w.WriteHeader(http.StatusConflict) 301 | 302 | return 303 | case posNat[0] == r.URL.Query().Get("position"): 304 | return 305 | default: 306 | w.WriteHeader(http.StatusNotFound) 307 | 308 | return 309 | } 310 | } 311 | var rulespecs []string 312 | if (r.URL.Query().Get("nth_every") != "") || (r.URL.Query().Get("nth_packet") != "") { 313 | if r.URL.Query().Get("nth_every") == "" { 314 | w.WriteHeader(http.StatusBadRequest) 315 | fmt.Fprintln(w, "Missing nth every") 316 | 317 | return 318 | } 319 | if r.URL.Query().Get("nth_packet") == "" { 320 | w.WriteHeader(http.StatusBadRequest) 321 | fmt.Fprintln(w, "Missing nth packet") 322 | 323 | return 324 | } 325 | } 326 | switch vars["action"] { 327 | case dnatAct: 328 | rulespecs = dnatGenerateV6(r) 329 | case snatAct: 330 | rulespecs = snatGenerateV6(r) 331 | default: 332 | w.WriteHeader(http.StatusNotFound) 333 | 334 | return 335 | } 336 | if ipt.HasWait { 337 | rulespecs = append(rulespecs, "--wait") 338 | } 339 | respStr, respErr := ipt.Exists("nat", vars["chain"], rulespecs...) 340 | if respErr != nil { 341 | w.WriteHeader(http.StatusBadRequest) 342 | fmt.Fprintln(w, respErr) 343 | } 344 | if !respStr { 345 | w.WriteHeader(http.StatusNotFound) 346 | } 347 | } 348 | --------------------------------------------------------------------------------