Skip to content

Instantly share code, notes, and snippets.

@kwilczynski
Created January 30, 2019 16:10
Show Gist options
  • Save kwilczynski/32213cfee4f9676ecdabaf09b6f7a581 to your computer and use it in GitHub Desktop.
Save kwilczynski/32213cfee4f9676ecdabaf09b6f7a581 to your computer and use it in GitHub Desktop.
Nessus Scanner Fact - collect and expose Nessus Scanner status to Ansible.
https://localhost
tls ./server.pem ./server.key {
ca ./ca.pem
clients require
protocols tls1.0 tls1.1
}
root .
log stdout
browse
package main
import (
"bytes"
"context"
"crypto/tls"
"crypto/x509"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"log"
"net"
"net/http"
"net/http/httputil"
"net/url"
"os"
"strings"
"time"
"github.com/hashicorp/logutils"
kingpin "gopkg.in/alecthomas/kingpin.v2"
)
const (
version = "0.1.0"
userAgent = "Nessus Scanner Fact"
defaultLogLevel = "INFO"
defaultHost = "localhost"
defaultScheme = "https"
defaultTimeout = 3 * time.Second
)
const (
exitReady = iota
exitLocked
exitError
exitUnknown = (1 << 8) - 1
)
var (
defaultPaths = []string{
"/server/status",
"/server/properties",
}
validTLSVersions = map[string]int{
"tls1.0": tls.VersionTLS10,
"tls1.1": tls.VersionTLS11,
"tls1.2": tls.VersionTLS12,
}
requiredFields = map[string]struct{}{
"code": struct{}{},
"progress": struct{}{},
"status": struct{}{},
}
)
type config struct {
debug *bool
trace *bool
logLevel *string
host *string
port *int
timeout *int
tlsDisable *bool
tlsVerify *bool
tlsVersion *string
tlsCertificate *string
tlsKey *string
tlsCA *string
exitCodeOnly *bool
}
func main() {
cliArgs := os.Args[1:]
envArgs := os.Getenv("NESSUS_FACT_ARGUMENTS")
if len(cliArgs) < 1 && envArgs != "" {
ss := strings.Split(envArgs, ",")
for _, s := range ss {
cliArgs = append(cliArgs, strings.Split(s, " ")...)
}
}
flags := kingpin.New("nessus.fact", "Nessus Scanner Fact - collect and expose Nessus Scanner status to Ansible.")
config := &config{
debug: flags.Flag("debug", "Print diagnostic information.").Bool(),
logLevel: flags.Flag("log-level", `Set the log level (defaults to "INFO").`).PlaceHolder("LEVEL").String(),
host: flags.Flag("host", `Host to connect to (defaults to "localhost").`).Short('h').String(),
port: flags.Flag("port", `Port to use when connecting to a given host (defaults to "443").`).Short('p').Int(),
timeout: flags.Flag("timeout", "Number of seconds to wait before timing out connection.").Short('t').PlaceHolder("SECONDS").Int(),
tlsDisable: flags.Flag("tls-disable", "Disable TLS support.").Short('T').Bool(),
tlsVerify: flags.Flag("tls-verify", "Enable peer TLS cerificate verification (disabled by default).").Bool(),
tlsVersion: flags.Flag("tls-version", `Specify which TLS version to use, from either "tls1.0", "tls1.1" or "tls1.2" (defaults to "tls1.2").`).PlaceHolder("VERSION").String(),
tlsCertificate: flags.Flag("tls-certificate", "Path to TLS certificate file.").PlaceHolder("PATH").String(),
tlsKey: flags.Flag("tls-key", "Path to TLS key file.").PlaceHolder("PATH").String(),
tlsCA: flags.Flag("tls-ca", "Path to CA file.").PlaceHolder("PATH").String(),
exitCodeOnly: flags.Flag("exit-code-only", "Do not print anything and only use exit code to communicate status.").Short('E').Bool(),
}
config.trace = &[]bool{false}[0]
flags.Version(version)
flags.UsageTemplate(customUsageTemplate)
kingpin.MustParse(flags.Parse(cliArgs))
filter := &logutils.LevelFilter{
Levels: []logutils.LogLevel{"TRACE", "DEBUG", "INFO", "WARN", "ERROR", "FATAL", "NONE"},
MinLevel: logutils.LogLevel(defaultLogLevel),
Writer: os.Stderr,
}
if *config.logLevel == "" {
*config.logLevel = os.Getenv("NESSUS_FACT_LOG_LEVEL")
}
if *config.logLevel == "" {
*config.logLevel = defaultLogLevel
}
*config.logLevel = strings.ToUpper(*config.logLevel)
if *config.exitCodeOnly {
*config.logLevel = "NONE"
}
if *config.logLevel == "TRACE" {
*config.trace = true
}
if *config.trace || *config.logLevel == "DEBUG" || os.Getenv("NESSUS_FACT_DEBUG") == "1" {
*config.debug = true
}
if *config.debug {
*config.logLevel = "DEBUG"
}
if *config.trace {
*config.logLevel = "TRACE"
}
validLogLevel := false
for _, level := range filter.Levels {
if string(level) == *config.logLevel {
validLogLevel = true
break
}
}
if !validLogLevel {
log.SetFlags(0)
log.Fatalf(
"An invalid log level %q given. See %s --help.",
*config.logLevel,
os.Args[0],
)
}
filter.SetMinLevel(logutils.LogLevel(*config.logLevel))
log.SetOutput(filter)
log.Printf("[DEBUG] Command line arguments: %v\n", cliArgs)
scheme := defaultScheme
if *config.tlsDisable {
scheme = "http"
}
host := defaultHost
if *config.host != "" {
host = *config.host
}
if *config.port > 0 {
host = fmt.Sprintf("%s:%d", host, *config.port)
}
timeout := defaultTimeout
if *config.timeout > 0 {
timeout = time.Duration(*config.timeout) * time.Second
}
tlsConfig := &tls.Config{
ServerName: host,
PreferServerCipherSuites: true,
InsecureSkipVerify: true,
MinVersion: tls.VersionTLS12,
}
if *config.tlsVersion != "" {
version, ok := validTLSVersions[*config.tlsVersion]
if !ok {
log.Fatalf("[FATAL] Unknown TLS version: %s", *config.tlsVersion)
}
log.Printf("[DEBUG] Using TLS version: %v\n", *config.tlsVersion)
tlsConfig.MinVersion = uint16(version)
}
if *config.tlsCertificate != "" && *config.tlsKey != "" {
cert, err := tls.LoadX509KeyPair(*config.tlsCertificate, *config.tlsKey)
if err != nil {
log.Fatalf("[FATAL] Unable to load TLS certificate: %s ", err)
}
log.Printf(
"[DEBUG] Using TLS certificate: %v, key: %v\n",
*config.tlsCertificate,
*config.tlsKey,
)
tlsConfig.Certificates = []tls.Certificate{cert}
}
if *config.tlsCA != "" {
ca, err := ioutil.ReadFile(*config.tlsCA)
if err != nil {
log.Fatalf("[FATAL] Unable to load CA certificate: %s", err)
}
log.Printf(
"[DEBUG] Using CA certificate: %v\n",
*config.tlsCA,
)
certPool := x509.NewCertPool()
certPool.AppendCertsFromPEM(ca)
tlsConfig.RootCAs = certPool
}
if *config.tlsVerify {
log.Println("[DEBUG] Using TLS certificate verification.")
tlsConfig.InsecureSkipVerify = false
}
tlsConfig.BuildNameToCertificate()
transport := &http.Transport{
Proxy: http.ProxyFromEnvironment,
Dial: (&net.Dialer{
Timeout: timeout,
}).Dial,
TLSHandshakeTimeout: 5 * time.Second,
TLSClientConfig: tlsConfig,
}
client := &http.Client{
Transport: transport,
CheckRedirect: func(*http.Request, []*http.Request) error {
return http.ErrUseLastResponse
},
}
var (
err error
dump []byte
url *url.URL
req *http.Request
resp *http.Response
)
result := make(map[string]interface{})
for _, path := range defaultPaths {
url, err = url.Parse(fmt.Sprintf("%s://%s%s", scheme, host, path))
if err != nil {
log.Fatalf("[FATAL] Unable to parse request URL: %s", err)
}
log.Printf("[DEBUG] Sending request to: %v\n", url.String())
req, err = http.NewRequest("GET", url.String(), http.NoBody)
if err != nil {
log.Fatalf("[FATAL] Unable to create a request: %s", err)
}
req.Header.Set("Accept", "*/*")
req.Header.Set("Date", time.Now().UTC().Format(time.RFC1123))
req.Header.Set("User-Agent", fmt.Sprintf("%s/%s", userAgent, version))
req.Header.Set("Content-Type", "application/json")
if *config.trace {
dump, err = httputil.DumpRequestOut(req, true)
if err != nil {
log.Printf("[TRACE] Unable to dump request: %v\n", err)
}
log.Printf("[TRACE] Request:\n%v\n", string(dump))
}
resp, err = client.Do(req.WithContext(
context.Background(),
))
if err != nil {
log.Fatalf("[FATAL] Sending request failed: %s", err)
}
if *config.trace {
dump, err = httputil.DumpResponse(resp, true)
if err != nil {
log.Printf("[TRACE] Unable to dump response: %v\n", err)
}
log.Printf("[TRACE] Response:\n%v\n", string(dump))
}
if resp.StatusCode >= http.StatusOK && resp.StatusCode < http.StatusMultipleChoices {
var body map[string]interface{}
err = json.NewDecoder(resp.Body).Decode(&body)
if err != nil {
log.Fatalf("[FATAL] Unable to decode JSON response: %s", err)
}
if len(body) < 1 {
log.Fatalln("[FATAL] Received empty response body or empty JSON document.")
}
for k, v := range body {
result[k] = v
}
}
err = resp.Body.Close()
if err != nil {
log.Fatalf("[FATAL] Failed to close response body: %s", err)
}
}
if *config.debug {
dump, err = json.MarshalIndent(result, "", " ")
if err != nil {
log.Printf("[DEBUG] Unable decode JSON document: %v\n", err)
}
log.Printf("[DEBUG] JSON document from response:\n%v\n", string(dump))
}
if len(result) < 1 {
log.Fatalln("[FATAL] Received empty JSON document.")
}
var seen, score int
for k := range requiredFields {
if _, ok := result[k]; ok {
score++
}
seen++
}
if score < seen {
log.Fatalln("[FATAL] Received JSON document is missing key fields.")
}
if *config.exitCodeOnly {
s, ok := result["status"]
if !ok {
os.Exit(exitUnknown)
}
switch s.(string) {
case "ready":
os.Exit(exitReady)
case "locked":
os.Exit(exitLocked)
default:
os.Exit(exitUnknown)
}
}
var (
version string
build string
)
if s, ok := result["server_version"]; ok {
version = s.(string)
}
if s, ok := result["nessus_ui_build"]; ok {
build = s.(string)
}
if version != "" && build != "" {
result["ops_version"] = fmt.Sprintf("%s-%s", version, build)
}
var b bytes.Buffer
err = json.NewEncoder(&b).Encode(result)
if err != nil {
log.Fatalf("[FATAL] Unable to encode JSON: %s", err)
}
_, err = io.Copy(os.Stdout, &b)
if err != nil {
panic("unreachable")
}
}
var customUsageTemplate = `{{define "FormatUsage"}}
{{ if .Help }}
{{ .Help | Wrap 0 }}\
{{ end }}\
{{ end }}\
Usage: {{.App.Name }} [<FLAGS> ...]{{ template "FormatUsage" .App }}
{{ if .Context.Flags }}\
Flags:
{{ .Context.Flags | FlagsToTwoColumns | FormatTwoColumns }}
{{ end }}\
`
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment