Skip to content

Commit

Permalink
Working PuppetDB API client
Browse files Browse the repository at this point in the history
Signed-off-by: Simon Plourde <[email protected]>
  • Loading branch information
palourde committed Feb 7, 2020
1 parent 742a51a commit 6d82aa4
Show file tree
Hide file tree
Showing 5 changed files with 219 additions and 82 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,6 @@ vendor/
# deploy
bonsai/
dist/

*.swp
*.pem
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ go 1.13

require (
github.com/robfig/cron v0.0.0-20171101201047-2315d5715e36 // indirect
github.com/sensu-community/sensu-plugin-sdk v0.5.0
github.com/sensu-community/sensu-plugin-sdk v0.6.0
github.com/sensu/sensu-go v0.0.0-20200131164840-40b1d5938251
)
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,10 @@ github.com/sensu-community/sensu-plugin-sdk v0.5.0 h1:riMWfYhtnDazNavhnOx2RU/QlK
github.com/sensu-community/sensu-plugin-sdk v0.5.0/go.mod h1:Qkxx5GWZ99Hc5O6nZfBj4bNYjIDR8Qv2qobR3LSYAyA=
github.com/sensu-community/sensu-plugin-sdk v0.5.1-0.20200205200245-9d11cf216bc0 h1:oSVSbEVmfDATbvUKlc3ERAposQr7or6FUcKLg6w27is=
github.com/sensu-community/sensu-plugin-sdk v0.5.1-0.20200205200245-9d11cf216bc0/go.mod h1:Qkxx5GWZ99Hc5O6nZfBj4bNYjIDR8Qv2qobR3LSYAyA=
github.com/sensu-community/sensu-plugin-sdk v0.5.1-0.20200206153623-a973644e147d h1:k2WN7X08sQ97e8GbytLHOZbLodRmfLY1R3Kw/s1CDsA=
github.com/sensu-community/sensu-plugin-sdk v0.5.1-0.20200206153623-a973644e147d/go.mod h1:Qkxx5GWZ99Hc5O6nZfBj4bNYjIDR8Qv2qobR3LSYAyA=
github.com/sensu-community/sensu-plugin-sdk v0.6.0 h1:ahYErlo3yfN7AlkkGX3TH1854hNXJ00MgUIW0sHaI3M=
github.com/sensu-community/sensu-plugin-sdk v0.6.0/go.mod h1:Qkxx5GWZ99Hc5O6nZfBj4bNYjIDR8Qv2qobR3LSYAyA=
github.com/sensu/lasr v1.2.1/go.mod h1:VIMtIK67Bcef6dTfctRCBg8EY9M9TtCY9NEFT6Zw5xQ=
github.com/sensu/sensu-go v0.0.0-20190508172758-32aea478ae74 h1:j8X+G1s42GdIcVWvNcbVy1vN8bsX24DADOW8FfOupk0=
github.com/sensu/sensu-go v0.0.0-20190508172758-32aea478ae74/go.mod h1:oP0w1f0WSgVPzGUhK5EmM774W/JaWEpj4zg9Kd972y8=
Expand Down
202 changes: 150 additions & 52 deletions main.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
package main

import (
"crypto/tls"
"crypto/x509"
"errors"
"fmt"
"io/ioutil"
"log"
"net/http"
"net/url"
"path"
"strings"

"github.com/sensu-community/sensu-plugin-sdk/sensu"
"github.com/sensu/sensu-go/types"
Expand All @@ -13,25 +19,26 @@ import (
// Handler represents the sensu-puppet-handler plugin
type Handler struct {
sensu.PluginConfig
endpoint string
keystoreFile string
keystorePassword string
truststoreFile string
truststorePassword string
httpProxy string
timeout int
endpoint string
puppetCert string
puppetKey string
puppetCACert string
puppetInsecureSkipVerify bool
sensuAPIURL string
sensuAPIKey string
sensuCACert string
}

const (
defaultAPIPath = "pdb/query/v4/nodes"
defaultAPIPath = "pdb/query/v4/nodes"
labelPuppetNodeName = "puppet_node_name"
)

var (
handler = Handler{
PluginConfig: sensu.PluginConfig{
Name: "sensu-puppet-handler",
Short: "Deregister Sensu entities without an associated Puppet node",
Timeout: 10,
Keyspace: "sensu.io/plugins/sensu-puppet-handler/config",
},
}
Expand All @@ -46,46 +53,57 @@ var (
Value: &handler.endpoint,
},
&sensu.PluginConfigOption{
Path: "keystore_file",
Env: "PUPPET_KEYSTORE_FILE",
Argument: "keystore_file",
Usage: "the file path for the SSL certificate keystore",
Value: &handler.keystoreFile,
Path: "cert",
Env: "PUPPET_CERT",
Argument: "cert",
Usage: "path to the SSL certificate PEM file signed by your site's Puppet CA",
Value: &handler.puppetCert,
},
&sensu.PluginConfigOption{
Path: "keystore_password",
Env: "PUPPET_KEYSTORE_PASSWORD",
Argument: "keystore_password",
Usage: "the SSL certificate keystore password",
Value: &handler.keystorePassword,
Path: "key",
Env: "PUPPET_KEY",
Argument: "key",
Usage: "path to the private key PEM file for that certificate",
Value: &handler.puppetKey,
},
&sensu.PluginConfigOption{
Path: "truststore_file",
Env: "PUPPET_TRUSTSTORE_FILE",
Argument: "truststore_file",
Usage: "the file path for the SSL certificate truststore",
Value: &handler.truststoreFile,
Path: "cacert",
Env: "PUPPET_CACERT",
Argument: "cacert",
Usage: "path to the site's Puppet CA certificate PEM file",
Value: &handler.puppetCACert,
},
&sensu.PluginConfigOption{
Path: "truststore_password",
Env: "PUPPET_TRUSTSTORE_PASSWORD",
Argument: "truststore_password",
Usage: "the SSL certificate truststore password",
Value: &handler.truststorePassword,
Path: "insecure-skip-tls-verify",
Env: "PUPPET_INSECURE_SKIP_TLS_VERIFY",
Argument: "insecure-skip-tls-verify",
Usage: "skip SSL verification",
Value: &handler.puppetInsecureSkipVerify,
},
&sensu.PluginConfigOption{
Path: "http_proxy",
Env: "PUPPET_HTTP_PROXY",
Argument: "http_proxy",
Usage: "the URL of a proxy to be used for HTTP requests",
Value: &handler.httpProxy,
{
Path: "sensu-api-url",
Env: "SENSU_API_URL",
Argument: "sensu-api-url",
Shorthand: "u",
Default: "http://localhost:8080",
Usage: "The Sensu API URL",
Value: &handler.sensuAPIURL,
},
&sensu.PluginConfigOption{
Path: "timeout",
Env: "PUPPET_TIMEOUT",
Argument: "timeout",
Usage: "the handler execution duration timeout in seconds (hard stop)",
Value: &handler.httpProxy,
{
Path: "sensu-api-key",
Env: "SENSU_API_KEY",
Argument: "sensu-api-key",
Shorthand: "a",
Usage: "The Sensu API key",
Value: &handler.sensuAPIKey,
},
{
Path: "sensu-ca-cert",
Env: "SENSU_CA_CERT",
Argument: "sensu-ca-cert",
Shorthand: "c",
Usage: "The Sensu Go CA Certificate",
Value: &handler.sensuCACert,
},
}
)
Expand All @@ -95,25 +113,30 @@ func main() {
handler.Execute()
}

func validate(_ *types.Event) error {
func validate(event *types.Event) error {
// Make sure we have a valid event
if event.Check == nil || event.Entity == nil {
return errors.New("invalid event")
}

// Make sure all required options are provided
if handler.endpoint == "" {
if len(handler.endpoint) == 0 {
return errors.New("the PuppetDB API endpoint is required")
}
if handler.keystoreFile == "" {
return errors.New("the path to the SSL certificate keystore is required")
if len(handler.puppetCert) == 0 {
return errors.New("the path to the SSL certificate is required")
}
if handler.keystorePassword == "" {
return errors.New("the SSL certificate keystore password is required")
if len(handler.puppetKey) == 0 {
return errors.New("the path to the private key is required")
}
if handler.truststoreFile == "" {
return errors.New("the path for the SSL certificate truststore is required")
if len(handler.sensuAPIURL) == 0 {
return errors.New("the Sensu API URL is required")
}
if handler.truststorePassword == "" {
return errors.New("the SSL certificate truststore password is required")
if len(handler.sensuAPIKey) == 0 {
return errors.New("the Sensu API key is required")
}

// Make sure the endpoint URL is valid
// Make sure the PuppetDB endpoint URL is valid
u, err := url.Parse(handler.endpoint)
if err != nil {
return fmt.Errorf("invalid PuppetDB API endpoint URL: %s", err)
Expand All @@ -126,9 +149,84 @@ func validate(_ *types.Event) error {
handler.endpoint = u.String()
}

// Make sure the Sensu API URL is valid
u, err = url.Parse(handler.sensuAPIURL)
if err != nil {
return fmt.Errorf("invalid Sensu API URL: %s", err)
}
if u.Scheme == "" || u.Host == "" {
return errors.New("invalid Sensu API URL")
}

return nil
}

func executeHandler(event *types.Event) error {
if event.Check.Name != "keepalive" {
log.Print("received non-keepalive event, not checking for puppet node")
return nil
}

exists, err := puppetNodeExists(event)
if err != nil {
return err
}
if exists {
log.Print("puppet node exists")
return nil
}

return nil
}

// puppetNodeExists returns whether a given node exists in Puppet and any error
// encountered. The Puppet node name defaults to the entity name but can be
// overriden through the entity label "puppet_node_name"
func puppetNodeExists(event *types.Event) (bool, error) {
// Determine the Puppet node name
name := event.Entity.Name
if event.Entity.Labels[labelPuppetNodeName] != "" {
name = event.Entity.Labels[labelPuppetNodeName]
}

// Load the public/private key pair
cert, err := tls.LoadX509KeyPair(handler.puppetCert, handler.puppetKey)
if err != nil {
return false, err
}

// Load the CA certificate
caCert, err := ioutil.ReadFile(handler.puppetCACert)
if err != nil {
log.Println(err.Error())
}
caCertPool := x509.NewCertPool()
caCertPool.AppendCertsFromPEM(caCert)

// Setup the HTTPS client
tlsConfig := &tls.Config{
Certificates: []tls.Certificate{cert},
RootCAs: caCertPool,
InsecureSkipVerify: handler.puppetInsecureSkipVerify,
}
tlsConfig.BuildNameToCertificate()
client := &http.Client{Transport: &http.Transport{TLSClientConfig: tlsConfig}}

// Get the puppet node
endpoint := strings.TrimRight(handler.endpoint, "/")
endpoint = fmt.Sprintf("%s/%s", endpoint, name)
resp, err := client.Get(endpoint)
if err != nil {
return false, err
}
_ = resp.Body.Close()

// Determine if the node exists
if resp.StatusCode == http.StatusOK {
return true, nil
} else if resp.StatusCode == http.StatusNotFound {
return false, nil
}

return false, fmt.Errorf("unexpected HTTP status %s while querying PuppetDB", http.StatusText(resp.StatusCode))
}
Loading

0 comments on commit 6d82aa4

Please sign in to comment.