main.go 6.01 KiB
package main
import (
"acme-web/certinspector"
"fmt"
"log"
"net/http"
"os"
"os/exec"
"path/filepath"
"strings"
"github.com/docopt/docopt-go"
"gopkg.in/ini.v1"
)
var (
appVersion string
buildTime string
baseDir string
webDir string
bearerToken string
WarningLogger *log.Logger
InfoLogger *log.Logger
ErrorLogger *log.Logger
verboseBool bool
baseURLs []string
apiURLs []string
otherURLs []string
)
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)
}
// serve certificates JSON
func renderJSON(w http.ResponseWriter, req *http.Request) {
provider := strings.Split(req.URL.Path, "/")[2]
jsonData, err := certinspector.ProcessCertificates(baseDir, provider)
if err != nil {
WarningLogger.Println(err)
http.Error(w, "Failed to process certificates", http.StatusServiceUnavailable)
return
}
// Write JSON response
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
_, err = w.Write(jsonData)
if err != nil {
WarningLogger.Printf("Failed to write response: %v", err)
}
}
// serve certificates list
func renderPage(w http.ResponseWriter, req *http.Request) {
provider := strings.Split(req.URL.Path, "/")[1]
outputDir := filepath.Join(webDir, provider)
serveFile := filepath.Join(webDir, req.URL.Path)
err := certinspector.ProcessCertificatesWrite(baseDir, provider, outputDir)
if err != nil {
WarningLogger.Println(err)
http.Error(w, "Failed to process certificates", http.StatusServiceUnavailable)
return
}
w.Header().Set("Content-Type", "text/html")
if verboseBool {
InfoLogger.Printf("Serving file: %s", serveFile)
}
http.ServeFile(w, req, serveFile)
}
// trigger puppet
func triggerPuppet(w http.ResponseWriter, req *http.Request) {
// content-type currently not working
cmd := exec.Command("/usr/bin/pkill", "-f", "/opt/puppetlabs/puppet/bin/puppet", "--signal", "SIGUSR1")
authToken := "BOFH"
_, ok := req.Header["Authorization"]
if ok {
authToken = strings.Split(req.Header.Get("Authorization"), "Bearer ")[1]
}
okMsg := fmt.Sprintln("{\n \"status\": \"OK\",\n \"response\": 200\n}")
unauthorizedMsg := fmt.Sprintln("{\n \"status\": \"Unauthorized\",\n \"response\": 401\n}")
unavailableMsg := fmt.Sprintln("{\n \"status\": \"KO\",\n \"response\": 503\n}")
w.Header().Set("Content-Type", "application/json; charset=utf-8")
if authToken != bearerToken {
http.Error(w, unauthorizedMsg, http.StatusUnauthorized)
} else {
err := cmd.Run()
if err != nil {
WarningLogger.Println(err)
http.Error(w, unavailableMsg, http.StatusServiceUnavailable)
} else {
if verboseBool {
InfoLogger.Printf("HTTP Status %v", http.StatusOK)
}
http.Error(w, okMsg, http.StatusOK)
}
}
}
// redirect to /by_name.html
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 [--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
--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 = cfg.Section("acme").Key("bearer_token").String()
// remove leading and trailing spaces and quotes and split by comma
acmeProvidersRaw := cfg.Section("acme").Key("acme_providers").String()
acmeProvidersRaw = strings.Trim(acmeProvidersRaw, "[] ")
acmeProviders := strings.Split(acmeProvidersRaw, ",")
for i := range acmeProviders {
acmeProviders[i] = strings.Trim(acmeProviders[i], "' ")
}
baseDir = "/etc"
webDir = "/var/www/acme_web"
verboseBool = arguments["--verbose"].(bool)
listenAddress := arguments["--listen-address"].(string)
listenPort := arguments["--listen-port"].(string)
for _, provider := range acmeProviders {
fmt.Printf("%v element\n", provider)
baseURLs = append(baseURLs, "/"+provider, "/"+provider+"/")
fmt.Printf("%v element\n", baseURLs)
}
for _, provider := range acmeProviders {
apiURLs = append(apiURLs, "/api/"+provider, "/api/"+provider+"/")
}
for _, provider := range acmeProviders {
otherURLs = append(
otherURLs, "/"+provider+"/by_name.html", "/"+provider+"/by_date.html",
"/"+provider+"/"+provider+".json", "/"+provider+"/"+provider+"_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))
}
}