Skip to content

Commit

Permalink
Pkcs12 support (#68)
Browse files Browse the repository at this point in the history
* added .p12 filetype to gitignore

* added additional parameters for pkcs12 support

* added additional config params for pkcs12 support.
To avoid breaking change, CertType will automatically set to PEM if not configured. This will ensure previous configurations will remain working even if  the new parameters are not defined. see line 77-81

* - "github.com/go-kit/kit/log" -> "github.com/go-kit/log" -> deprecation warning
  - added kingpin flags for pkcs12 parameters
   - added support for pkcs12 decoding and using for http.ListenAndServeTLS
   - specifying min tls version 1.2, ciphers and preferences

* moved tls logic to separate file

* fixed typo

* extended README with PKCS12 relevant configuration options/examples
  • Loading branch information
GyroGearl00se authored Mar 12, 2024
1 parent 1d88bb1 commit 84d2306
Show file tree
Hide file tree
Showing 8 changed files with 223 additions and 22 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ solace_prometheus_exporter
.idea
*.iml
*.pem
*.p12
75 changes: 68 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,9 @@ Flags:
--enable-tls Set to true, to start listenAddr as TLS port. Make sure to provide valid server certificate and private key files.
--certificate=CERTIFICATE If using TLS, you must provide a valid server certificate in PEM format. Can be set via config file, cli parameter or env variable.
--private-key=PRIVATE-KEY If using TLS, you must provide a valid private key in PEM format. Can be set via config file, cli parameter or env variable.
--cert-type=CERT-TYPE Set the certificate type PEM | PKCS12.
--pkcs12File=PKCS12FILE If using TLS, you must provide a valid pkcs12 file.
--pkcs12Pass=PKCS12PASS If using TLS, you must provide a valid pkcs12 password.
```

The configuration parameters can be placed into a config file or into a set of environment variables or can be given via
Expand All @@ -264,17 +267,29 @@ Sample config file:
# Address to listen on for web interface and telemetry.
listenAddr = 0.0.0.0:9628

# Enable TLS on listenAddr endpoint. Make sure to provide certificate and private key files.
# can be overridden via env variable SOLACE_LISTEN_TLS
enableTLS = true
# Enable TLS on listenAddr endpoint. Make sure to provide certificate and private key files when using certType=PEM or or PKCS12 file and password when using PKCS12.
# can be overridden via env variable SOLACE_LISTEN_TLS or via cli parameter --enable-tls
enableTLS=false

# Path to the server certificate (including intermediates and CA's certificate)
# can be overridden via env variable SOLACE_SERVER_CERT
certificate = cert.pem
# can be overridden via env variable SOLACE_SERVER_CERT or via cli parameter --certificate=cert.pem
certificate=cert.pem

# Path to the private key pem file
# can be overridden via env variable SOLACE_PRIVATE_KEY
privateKey = key.pem
# can be overridden via env variable SOLACE_PRIVATE_KEY or via cli parameter --private-key=key.pem
privateKey=key.pem

# Set the certificate type PEM | PKCS12. Make sure to provide certificate and private key files for PEM or PKCS12 file and password.
# can be overridden via env variable SOLACE_LISTEN_CERTTYPE or via cli parameter --cert-type
certType=PEM

# Path to the server certificate (including intermediates and CA's certificate)
# can be overridden via env variable SOLACE_PKCS12_FILE or via cli parameter --pkcs12File=keystore.p12
pkcs12File=keystore.p12

# Password to decrypt PKCS12 file.
# can be overridden via env variable SOLACE_PKCS12_PASS or via cli parameter --pkcs12Pass=passwordHere
pkcs12Pass=123456

# Base URI on which to scrape Solace broker.
scrapeUri = http://your-exporter:8080
Expand Down Expand Up @@ -314,6 +329,9 @@ SOLACE_LISTEN_ADDR=0.0.0.0:9628
SOLACE_LISTEN_TLS=true
SOLACE_SERVER_CERT=/path/to/your/cert.pem
SOLACE_PRIVATE_KEY=/path/to/your/key.pem
SOLACE_LISTEN_CERTTYPE=PEM
SOLACE_PKCS12_FILE=/path/to/your/keystore.p12
SOLACE_PKCS12_PASS=123456
SOLACE_SCRAPE_URI=http://your-broker:8080
SOLACE_USERNAME=admin
SOLACE_PASSWORD=admin
Expand Down Expand Up @@ -420,6 +438,10 @@ or cli flag `--enable-tls` respectively.
TLS encryption requires you to provide two files in PEM (base64) format. You can define the path to those files in
different ways:

- Certificate Type (If not defined, it'll defaut to PEM)
- __cli parameter__: `--cert-type=PEM`
- __environment variable__: `SOLACE_LISTEN_CERTTYPE=PEM`
- __config file__: `certType=PEM`
- Server certificate (including intermediates and CA's certificate)
- __cli parameter__: `--certificate=cert.pem`
- __environment variable__: `SOLACE_SERVER_CERT`
Expand Down Expand Up @@ -450,6 +472,45 @@ SOLACE_SERVER_CERT=/etc/solace/cert.pem
SOLACE_PRIVATE_KEY=/etc/solace/key.pem
```

### Alternatively you can also use a P12 Keystore (PKCS12).
You can define the path and the password in different ways:

- Certificate Type (If not defined, it'll defaut to PEM)
- __cli parameter__: `--cert-type=PKCS12`
- __environment variable__: `SOLACE_LISTEN_CERTTYPE=PKCS12`
- __config file__: `certType=PKCS12`
- Path to PKCS12 Keystore File
- __cli parameter__: `--pkcs12File=keystore.p12`
- __environment variable__: `SOLACE_PKCS12_FILE=/path/to/your/keystore.p12`
- __config file__: `pkcs12File=keystore.p12`
- Password for PCS12 Keystore
- __cli parameter__: `--pkcs12Pass=123456`
- __environment variable__: `SOLACE_PKCS12_PASS=123456`
- __config file__: `pkcs12Pass=123456`

If you're running the exporter via Docker container, you can map the keystore file during your `docker run` command
from the host to the container.

```bash
docker run -d \
-p 9628:9628 \
--env-file env.txt \
-v ${PWD}/keystore.p12:/etc/solace/keystore.p12 \
[...]
solacecommunity/solace-prometheus-exporter
```

Of course, make sure to set the right local path and the pssword in the `env.txt` provided.

```.env
SOLACE_LISTEN_TLS=true
SOLACE_LISTEN_CERTTYPE=PKCS12
SOLACE_PKCS12_FILE=/etc/solace/keystore.p12
SOLACE_PKCS12_PASS=123456
```



## Resources

For more information, try these resources:
Expand Down
25 changes: 23 additions & 2 deletions exporter/config.struct.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ type Config struct {
EnableTLS bool
Certificate string
PrivateKey string
CertType string
Pkcs12File string
Pkcs12Pass string
ScrapeURI string
Username string
Password string
Expand All @@ -32,6 +35,11 @@ type Config struct {
logBrokerToSlowWarnings bool
}

const (
CERTTYPE_PEM = "PEM"
CERTTYPE_PKCS12 = "PKCS12"
)

// getListenURI returns the `listenAddr` with proper protocol (http/https),
// based on the `enableTLS` configuration parameter
func (c *Config) GetListenURI() string {
Expand Down Expand Up @@ -66,12 +74,25 @@ func ParseConfig(configFile string) (map[string][]DataSource, *Config, error) {
if err != nil {
return nil, nil, err
}
conf.Certificate, err = parseConfigString(cfg, "solace", "certificate", "SOLACE_SERVER_CERT")
conf.CertType, err = parseConfigString(cfg, "solace", "certType", "SOLACE_LISTEN_CERTTYPE")
if conf.EnableTLS && err != nil {
fmt.Println("CertType not set. Using default PEM")
conf.CertType = CERTTYPE_PEM
}
conf.Certificate, err = parseConfigString(cfg, "solace", "certificate", "SOLACE_SERVER_CERT")
if conf.EnableTLS && strings.ToUpper(conf.CertType) == CERTTYPE_PEM && err != nil {
return nil, nil, err
}
conf.PrivateKey, err = parseConfigString(cfg, "solace", "privateKey", "SOLACE_PRIVATE_KEY")
if conf.EnableTLS && err != nil {
if conf.EnableTLS && strings.ToUpper(conf.CertType) == CERTTYPE_PEM && err != nil {
return nil, nil, err
}
conf.Pkcs12File, err = parseConfigString(cfg, "solace", "pkcs12File", "SOLACE_PKCS12_FILE")
if conf.EnableTLS && strings.ToUpper(conf.CertType) == CERTTYPE_PKCS12 && err != nil {
return nil, nil, err
}
conf.Pkcs12Pass, err = parseConfigString(cfg, "solace", "pkcs12Pass", "SOLACE_PKCS12_PASS")
if conf.EnableTLS && strings.ToUpper(conf.CertType) == CERTTYPE_PKCS12 && err != nil {
return nil, nil, err
}
conf.ScrapeURI, err = parseConfigString(cfg, "solace", "scrapeUri", "SOLACE_SCRAPE_URI")
Expand Down
83 changes: 83 additions & 0 deletions exporter/tlsServer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package exporter

import (
"crypto/tls"
"net/http"
"os"
"strings"

"github.com/go-kit/log/level"
"github.com/prometheus/common/promlog"
"software.sslmate.com/src/go-pkcs12"
)

func ListenAndServeTLS(conf Config) {

promlogConfig := promlog.Config{
Level: &promlog.AllowedLevel{},
Format: &promlog.AllowedFormat{},
}
promlogConfig.Level.Set("info")
promlogConfig.Format.Set("logfmt")

logger := promlog.New(&promlogConfig)

var tlsCert tls.Certificate

if strings.ToUpper(conf.CertType) == CERTTYPE_PKCS12 {

// Read byte data from pkcs12 keystore
p12_data, err := os.ReadFile(conf.Pkcs12File)
if err != nil {
level.Error(logger).Log("Error reading PKCS12 file", err)
return
}

// Extract cert and key from pkcs12 keystore
privateKey, leafCert, caCerts, err := pkcs12.DecodeChain(p12_data, conf.Pkcs12Pass)
if err != nil {
level.Error(logger).Log("PKCS12 - Error decoding chain", err)
return
}

certBytes := [][]byte{leafCert.Raw}
for _, ca := range caCerts {
certBytes = append(certBytes, ca.Raw)
}
tlsCert = tls.Certificate{
Certificate: certBytes,
PrivateKey: privateKey,
}
} else {
var err error
tlsCert, err = tls.LoadX509KeyPair(conf.Certificate, conf.PrivateKey)
if err != nil {
level.Error(logger).Log("PEM - Error loading keypair", err)
return
}
}

cfg := &tls.Config{
MinVersion: tls.VersionTLS12,
CurvePreferences: []tls.CurveID{tls.CurveP521, tls.CurveP384, tls.CurveP256},
PreferServerCipherSuites: true,
CipherSuites: []uint16{
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
tls.TLS_RSA_WITH_AES_256_GCM_SHA384,
tls.TLS_RSA_WITH_AES_256_CBC_SHA,
},
Certificates: []tls.Certificate{tlsCert},
}
http := &http.Server{
Addr: conf.ListenAddr,
TLSConfig: cfg,
TLSNextProto: make(map[string]func(*http.Server, *tls.Conn, http.Handler), 0),
}

if err := http.ListenAndServeTLS("", ""); err != nil {
level.Error(logger).Log("msg", "Error starting HTTP server", "err", err)
os.Exit(2)
}

}
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ require (
gopkg.in/ini.v1 v1.67.0
)

require golang.org/x/crypto v0.11.0 // indirect

require (
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 // indirect
github.com/alecthomas/units v0.0.0-20231202071711-9a357b53e9c9 // indirect
Expand All @@ -24,4 +26,5 @@ require (
github.com/xhit/go-str2duration/v2 v2.1.0 // indirect
golang.org/x/sys v0.15.0 // indirect
google.golang.org/protobuf v1.31.0 // indirect
software.sslmate.com/src/go-pkcs12 v0.4.0
)
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
github.com/xhit/go-str2duration/v2 v2.1.0 h1:lxklc02Drh6ynqX+DdPyp5pCKLUQpRT8bp8Ydu2Bstc=
github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU=
golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA=
golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=
golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE=
golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
Expand All @@ -63,3 +65,5 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
software.sslmate.com/src/go-pkcs12 v0.4.0 h1:H2g08FrTvSFKUj+D309j1DPfk5APnIdAQAB8aEykJ5k=
software.sslmate.com/src/go-pkcs12 v0.4.0/go.mod h1:Qiz0EyvDRJjjxGyUQa2cCNZn/wMyzrRJ/qcDXOQazLI=
34 changes: 25 additions & 9 deletions solace_prometheus_exporter.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,21 +14,21 @@ package main

import (
"bytes"
"golang.org/x/sync/semaphore"
"net/http"
"os"
"solace_exporter/exporter"
"strings"
"time"

"github.com/alecthomas/kingpin/v2"
"github.com/go-kit/kit/log"
"github.com/go-kit/kit/log/level"
"github.com/go-kit/log"
"github.com/go-kit/log/level"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
"github.com/prometheus/common/promlog"
"github.com/prometheus/common/promlog/flag"
promVersion "github.com/prometheus/common/version"
"golang.org/x/sync/semaphore"
)

const version = float64(1004005)
Expand Down Expand Up @@ -68,7 +68,18 @@ func main() {
"private-key",
"If using TLS, you must provide a valid private key in PEM format. Can be set via config file, cli parameter or env variable.",
).ExistingFile()

certType := kingpin.Flag(
"cert-type",
" Set the certificate type PEM | PKCS12.",
).String()
pkcs12File := kingpin.Flag(
"pkcs12File",
"If using TLS, you must provide a valid pkcs12 file.",
).ExistingFile()
pkcs12Pass := kingpin.Flag(
"pkcs12Pass",
"If using TLS, you must provide a valid pkcs12 password.",
).String()
kingpin.Parse()

logger := promlog.New(&promlogConfig)
Expand All @@ -91,7 +102,15 @@ func main() {
if len(*privateKey) > 0 {
conf.PrivateKey = *privateKey
}

if len(*certType) > 0 {
conf.CertType = *certType
}
if len(*pkcs12File) > 0 {
conf.Pkcs12File = *pkcs12File
}
if len(*pkcs12Pass) > 0 {
conf.Pkcs12Pass = *pkcs12Pass
}
level.Info(logger).Log("msg", "Scraping",
"listenAddr", conf.GetListenURI(),
"scrapeURI", conf.ScrapeURI,
Expand Down Expand Up @@ -224,10 +243,7 @@ func main() {

// start server
if conf.EnableTLS {
if err := http.ListenAndServeTLS(conf.ListenAddr, conf.Certificate, conf.PrivateKey, nil); err != nil {
level.Error(logger).Log("msg", "Error starting HTTP server", "err", err)
os.Exit(2)
}
exporter.ListenAndServeTLS(*conf)
} else {
if err := http.ListenAndServe(conf.ListenAddr, nil); err != nil {
level.Error(logger).Log("msg", "Error starting HTTP server", "err", err)
Expand Down
20 changes: 16 additions & 4 deletions solace_prometheus_exporter.ini
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,30 @@
# Address to listen on for web interface and telemetry.
listenAddr=0.0.0.0:9628

# Enable TLS on listenAddr endpoint. Make sure to provide certificate and private key files.
# can be overridden via env varibale SOLACE_LISTEN_TLS or via cli parameter --enable-tls
# Enable TLS on listenAddr endpoint. Make sure to provide certificate and private key files when using certType=PEM or or PKCS12 file and password when using PKCS12.
# can be overridden via env variable SOLACE_LISTEN_TLS or via cli parameter --enable-tls
enableTLS=false

# Path to the server certificate (including intermediates and CA's certificate)
# can be overridden via env varibale SOLACE_SERVER_CERT or via cli parameter --certificate=cert.pem
# can be overridden via env variable SOLACE_SERVER_CERT or via cli parameter --certificate=cert.pem
certificate=cert.pem

# Path to the private key pem file
# can be overridden via env varibale SOLACE_PRIVATE_KEY or via cli parameter --private-key=key.pem
# can be overridden via env variable SOLACE_PRIVATE_KEY or via cli parameter --private-key=key.pem
privateKey=key.pem

# Set the certificate type PEM | PKCS12. Make sure to provide certificate and private key files for PEM or PKCS12 file and password.
# can be overridden via env variable SOLACE_LISTEN_CERTTYPE or via cli parameter --cert-type
certType=PEM

# Path to the server certificate (including intermediates and CA's certificate)
# can be overridden via env variable SOLACE_PKCS12_FILE or via cli parameter --pkcs12File=keystore.p12
pkcs12File=keystore.p12

# Password to decrypt PKCS12 file.
# can be overridden via env variable SOLACE_PKCS12_PASS or via cli parameter --pkcs12Pass=passwordHere
pkcs12Pass=123456

# Base URI on which to scrape Solace broker.
scrapeUri=https://mr-connection-j6em7yuyi7k.messaging.solace.cloud:943

Expand Down

0 comments on commit 84d2306

Please sign in to comment.