/* * nftables-http-api * Copyright (C) 2024 Georg Pfuetzenreuter * * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. * You should have received a copy of the GNU General Public License along with this program. If not, see . */ package main import ( "encoding/json" "flag" "github.com/gorilla/mux" "gopkg.in/yaml.v3" "log" "net/http" "os" ) var config Config var configFile string var listen string func init() { flag.StringVar(&configFile, "config", "/etc/nft-set-api.yml", "Path to configuration file") flag.StringVar(&listen, "listen", "[::1]:8082", "Address and port to listen on") } type Config struct { TokenSets map[string][]string } type authMiddleWareMap struct { tokenSets map[string]string } func (authMiddleWare *authMiddleWareMap) Middleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { var givenToken = r.Header.Get("TOKEN") params := mux.Vars(r) givenSet := params["set"] if givenToken == "" { doReturn(w, http.StatusUnauthorized, "missing token") return } for configToken, configSets := range config.TokenSets { if doCheckToken(givenToken, configToken) { for _, configSet := range configSets { if givenSet == configSet { next.ServeHTTP(w, r) return } } } } log.Printf("Not processing unauthenticated request from %s", r.RemoteAddr) doReturn(w, http.StatusUnauthorized, "invalid token") }) } type addressPayload struct { Address string } func main() { flag.Parse() log.Print("Booting ...") buffer, err := os.ReadFile(configFile) if err != nil { log.Fatalln("Could not read configuration file:", err) return } err = yaml.Unmarshal(buffer, &config) if err != nil { log.Fatalln("Could not parse configuration file:", err) return } log.Printf("%+v\n", config) log.Print("Listening on ", listen) router := mux.NewRouter() router.HandleFunc("/set/{set}", handleSetRoute).Methods("GET", "POST") authMiddleWare := authMiddleWareMap{make(map[string]string)} router.Use(authMiddleWare.Middleware) http.ListenAndServe(listen, router) } func handleSetRoute(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") params := mux.Vars(r) method := r.Method set := params["set"] log.Printf("Processing authorized %s request from %s for set %s", method, r.RemoteAddr, set) switch method { case "GET": nftResult, err := handleNft("get", set, "") if err != nil || nftResult == nil { doReturn(w, http.StatusInternalServerError, err.Error()) return } doReturnSet(w, http.StatusOK, "", nftResult.([]string)) case "POST": var payload addressPayload decErr := json.NewDecoder(r.Body).Decode(&payload) if decErr != nil { doReturn(w, http.StatusBadRequest, decErr.Error()) return } nftResult, err := handleNft("add", set, payload.Address) if err != nil || nftResult == nil { doReturn(w, http.StatusInternalServerError, err.Error()) return } switch nftResult { case "already": doReturn(w, http.StatusOK, "already exists") case "added": doReturn(w, http.StatusCreated, "ok") default: doReturn(w, http.StatusInternalServerError, "unhandled result") } } }