package main import ( "fmt" "log" "net/http" "os" "os/exec" "path/filepath" "strings" "github.com/docopt/docopt-go" "gopkg.in/ini.v1" ) var ( appVersion string buildTime string webDir string jsonConverter string bearerToken string WarningLogger *log.Logger InfoLogger *log.Logger ErrorLogger *log.Logger verboseBool bool ) func init() { InfoLogger = log.New(os.Stdout, "INFO: ", log.Ldate|log.Ltime) WarningLogger = log.New(os.Stdout, "WARNING: ", log.Ldate|log.Ltime) ErrorLogger = log.New(os.Stdout, "ERROR: ", log.Ldate|log.Ltime) } // write to file func writeFile(fileContent string, filePath string) { content := []byte(fileContent) err := os.WriteFile(filePath, content, 0644) if err != nil { WarningLogger.Println(err) } } // serve certificates JSON func renderJSON(w http.ResponseWriter, req *http.Request) { provider := strings.Split(req.URL.Path, "/")[2] serveFile := fmt.Sprintf("%v/%v/%v.json", webDir, provider, provider) cmd := exec.Command(jsonConverter, "-p", provider) err := cmd.Run() if err != nil { WarningLogger.Println(err) w.WriteHeader(http.StatusServiceUnavailable) } else { if verboseBool { InfoLogger.Printf("HTTP Status %v", http.StatusOK) } w.Header().Set("Content-Type", "application/json") } http.ServeFile(w, req, serveFile) } // serve certificates list func renderPage(w http.ResponseWriter, req *http.Request) { provider := strings.Split(req.URL.Path, "/") serveFile := filepath.Join(webDir, req.URL.Path) cmd := exec.Command(jsonConverter, "-p", provider[1]) err := cmd.Run() if err != nil { WarningLogger.Println(err) w.WriteHeader(http.StatusServiceUnavailable) } else { if verboseBool { InfoLogger.Printf("HTTP Status %v", http.StatusOK) } w.Header().Set("Content-Type", "text/html") } http.ServeFile(w, req, serveFile) } // trigger puppet func triggerPuppet(w http.ResponseWriter, req *http.Request) { //cmd := exec.Command("/usr/bin/pkill", "-f", "/opt/puppetlabs/puppet/bin/puppet", "-s", "SIGUSR1") cmd := exec.Command("/usr/bin/touch", "/TEST") authToken := strings.Split(req.Header.Get("Authorization"), "Bearer ")[1] statusFile := "/tmp/200.json" ok := fmt.Sprintf("{\n \"status\": \"OK\",\n \"response\": 200\n \"token\": %v\n}", authToken) ko := fmt.Sprintf("{\n \"status\": \"OK\",\n \"response\": 401\n \"token\": %v\n}", authToken) writeFile(ok, "/tmp/200.json") writeFile(ko, "/tmp/401.json") w.Header().Set("Content-Type", "application/json") if authToken != bearerToken { statusFile = "/tmp/401.json" //w.WriteHeader(http.StatusUnauthorized) http.Error(w, ko, http.StatusUnauthorized) http.ServeFile(w, req, "/tmp/401.json") //http.ServeFile(w, req, statusFile) } else { cmd.Run() if verboseBool { InfoLogger.Printf("HTTP Status %v", http.StatusOK) } http.ServeFile(w, req, statusFile) } } // function redirect func redirect(w http.ResponseWriter, req *http.Request) { redirectURL := filepath.Join(req.URL.Path, "/by_name.html") http.Redirect(w, req, redirectURL, http.StatusMovedPermanently) } func main() { progName := filepath.Base(os.Args[0]) usage := fmt.Sprintf(`ACME Web: - serve ACME HTML pages, trigger Puppet, expose API Usage: %v [--json-converter=JSONCONVERTER] [--listen-address=LISTENADDRESS] [--listen-port=LISTENPORT] [--verbose] %v -h | --help %v -b | --build %v -v | --version Options: -h --help Show this screen -b --build Print version and build information and exit -v --version Print version information and exit --json-converter=JSONCONVERTER Path to json converter script [default: /usr/bin/cert2json.py] --listen-address=LISTENADDRESS Web server address. Check Go net/http documentation [default: any] --listen-port=LISTENPORT Web server port [default: 8000] --verbose Log also successful connections `, progName, progName, progName, progName) arguments, _ := docopt.ParseArgs(usage, nil, appVersion) if arguments["--build"] == true { fmt.Printf("%v version: %v, built on: %v\n", progName, appVersion, buildTime) os.Exit(0) } cfg, err := ini.Load("/root/.acme.ini") if err != nil { fmt.Printf("Fail to read file: %v", err) os.Exit(1) } //bearerToken = fmt.Sprintf("Bearer %v", cfg.Section("acme").Key("bearer_token").String()) bearerToken = cfg.Section("acme").Key("bearer_token").String() webDir = "/var/www/acme_web" jsonConverter = arguments["--json-converter"].(string) verboseBool = arguments["--verbose"].(bool) listenAddress := arguments["--listen-address"].(string) listenPort := arguments["--listen-port"].(string) baseURLs := [6]string{"/sectigo_ev", "/sectigo_ov", "/letsencrypt", "/sectigo_ev/", "/sectigo_ov/", "/letsencrypt/"} apiURLs := [6]string{"/api/sectigo_ev", "/api/sectigo_ov", "/api/letsencrypt", "/api/sectigo_ev/", "/api/sectigo_ov/", "/api/letsencrypt/"} otherURLs := [12]string{"/letsencrypt/by_name.html", "/letsencrypt/by_date.html", "/letsencrypt/letsencrypt.json", "/letsencrypt/letsencrypt_expired.json", "/sectigo_ov/by_name.html", "/sectigo_ov/by_date.html", "/sectigo_ov/sectigo_ov.json", "/sectigo_ov/sectigo_ov_expired.json", "/sectigo_ev/by_name.html", "/sectigo_ev/by_date.html", "/sectigo_ev/sectigo_ev.json", "/sectigo_ev/sectigo_ev_expired.json"} puppetURL := "/puppet" fs := http.FileServer(http.Dir("/var/www/acme_web/static")) http.Handle("/static/", http.StripPrefix("/static/", fs)) http.HandleFunc(puppetURL, triggerPuppet) http.HandleFunc("/", func(res http.ResponseWriter, req *http.Request) { http.ServeFile(res, req, filepath.Join(webDir, "index.html")) }) for _, apiElement := range apiURLs { http.HandleFunc(apiElement, renderJSON) } for _, element := range baseURLs { http.HandleFunc(element, redirect) } for _, otherElement := range otherURLs { http.HandleFunc(otherElement, renderPage) } if listenAddress == "any" { log.Fatal(http.ListenAndServe(fmt.Sprintf(":%v", listenPort), nil)) } else { log.Fatal(http.ListenAndServe(fmt.Sprintf("%v:%v", listenAddress, listenPort), nil)) } }