Skip to content
Snippets Groups Projects
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))
	}
}