Skip to content

Commit

Permalink
String based certificates
Browse files Browse the repository at this point in the history
Signed-off-by: Raul Sevilla <rsevilla@redhat.com>
  • Loading branch information
rsevilla87 committed Aug 10, 2021
1 parent d34c111 commit a225569
Show file tree
Hide file tree
Showing 4 changed files with 88 additions and 33 deletions.
49 changes: 43 additions & 6 deletions docs/measurements.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ For example, the example below establish a threshold of 2000ms in the P99 metric

Latency thresholds are evaluated at the end of each job, showing an informative message like the following:

```
```log
INFO[2020-12-15 12:37:08] Evaluating latency thresholds
WARN[2020-12-15 12:37:08] P99 Ready latency (2929ms) higher than configured threshold: 2000ms
```
Expand All @@ -116,15 +116,19 @@ This measurement takes care of collecting golang profiling information from pods

As some components require authentication to get profiling information, `kube-burner` provides two different methods to address it:

- bearerToken: This variable holds a valid Bearer token which used by cURL to get pprof data. This method is usually valid with kube-apiserver and kube-controller-managers components
- cert + key: These variables point to a local certificate and private key files respectively. These files are copied to the remote pods and used by cURL to get pprof data. This method is usually valid with etcd.
- Bearer token authentication: This modality is configured by the variable `bearerToken`, which holds a valid Bearer token that will be used by cURL to get pprof data. This method is usually valid with kube-apiserver and kube-controller-managers components
- Certificate Authentication, usually valid for etcd: This method can be configured using a combination of cert/privKey files or directly using the cert/privkey content, it can be tweaked with the following variables:
- cert: Certificate string. The content of this string is written to the file `/tmp/pprof.crt` in the remote pods.
- key: Privete key string. The content of this string is written to the file `/tmp/pprof.key` in the remote pods.
- certFile: Path to a certificate file. The content of this file is copied to the remote pods to the path `/tmp/pprof.crt`
- keyFile: Path to a private key file. The content of this file is copied to the remote pods to the path `/tmp/pprof.key`

An example of how to configure this measurement to collect pprof HEAP and CPU profiling data from kube-apiserver and etcd is shown below:

```yaml
measurements:
- name: pprof
pprofInterval: 5m
pprofInterval: 30m
pprofDirectory: pprof-data
pprofTargets:
- name: kube-apiserver-heap
Expand All @@ -142,9 +146,42 @@ An example of how to configure this measurement to collect pprof HEAP and CPU pr
- name: etcd-heap
namespace: "openshift-etcd"
labelSelector: {app: etcd}
cert: etcd-peer-pert.crt
key: etcd-peer-pert.key
certFile: etcd-peer-pert.crt
keyFile: etcd-peer-pert.key
url: https://localhost:2379/debug/pprof/heap

- name: etcd-cpu
namespace: "openshift-etcd"
labelSelector: {app: etcd}
cert: |
-----BEGIN CERTIFICATE-----
rztlngowHwYDVR0jBB/asdfadsSDFDSKJEZz61mN8MKux9xciz0wgeMGA1UdEQSB
2zCB2IIUZXRjZCFwerdsvadsc3RlbS5zdmOCImV0Y2Qua3ViZS1zeXN0ZW0uc3Zj
LmNsdXN0ZXIubG9jYWyCF2V0Y2Qub3BlbnNoaWZ0LWV0Y2Quc3ZjgiVldGNkLm9w
ZW5zaGlmdC1ldGNkLnN2Yy5jbHVzdGVyLmxvY2Fsgglsb2NhbGhvc3SCAzo6MYIM
MTAuMC4xNDEuMjI4ggkxMjcuMC4wLjGCAzo6MYcQAAAAAAAAAAAAAAAAAAAAAYcE
CgCN5IcEfwAAAYcQAAAFFFF098asdfNkS23123ANBgkqhkiG9w0BAQsFAAOCAQEA
vlVzkjOIquj3uS0QxeixXWyCl8vApYanD/LqUN7ARSH5+vQ+Pw3qQJm1tdP3b7Yh
6NZC1DuA/n/kwWOMXtfsbaobZjV5XlPKjp4Oz7SIFE4P8ZmyYxTNSordmbkS4ezS
3QpJOF34IgyqxojTbQkWy4l3PZfpAt7ypWrbQiX8N9cSIKFicJN+uAzeDDM2qsh3
Xxk2PQtmmxpfOe9VaAj1dnGg9YNiF7ECuOD8nZsctmx79rPeBOzI5N093op/eFNb
yTE03WjyNP82xfiQnFfZQvqX6niasdf987ASfd4X1yh00ymxpK/cG1s7BPLSpaL1
xCMBdF9nD3tq9v/oyKJjvQ==
-----END CERTIFICATE-----
key: |
-----BEGIN RSA PRIVATE KEY-----
FOOvpAIBAAKCAQEAruV1jbKMqi5XfCaz7Ey6Bn5XGYOJvYc4VWKpT4OZ/TzBarDO
Okou0FU04klDK06Zajm13NKHDZjzhzcskO9xKQvFDanR17zqV0aX03MZR4pydbUv
WLKSE9KAKA823298gnkeQ9sorUwLrtjnFHdUDwfagIOffmPiZPIFM1hABZ50B1FV
xXCRdaAAIGKYvAg0ZcarE4LXSasdf/asdSk32kVMd2t7unA2nAIcxJmik4AcZxok
oIgOIIIIIIL1LOToA0XYPxkuOcWTpIg6XCVQuI104SKZ9K7yZSWmEBm3E2uSWz++
x/lYpw/vEjaWeaK9qdmCjXud+jceiCLjmiJF4QIDAQABAoIBADgvowo4eBQb+yL5
VAfvxjtbzyN1LITkseZMYdQXlRrTr9dUoYv8VPm8xdaEbr207HhBvfkI8TYfEu03
fmu5YIMtMsrm6XEDUc1j8laNvWtMQOUrpeA6zc7sJhvS5ys09gM6H+RwvaqeqYos
SGA8zZZekYWDw3NZJ1wCnEUYbsjeEE298fjLfvs7vk5AswD2yyX2fF4wEBLwMi0L
-----END RSA PRIVATE KEY-----
url: https://localhost:2379/debug/pprof/profile?timeout=30

```

**Note**: As mentioned before, this measurement requires the `curl` command to be available in the target pods.
2 changes: 1 addition & 1 deletion docs/metrics.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ INFO[2021-06-23 12:07:08] Metrics tarball generated at kube-burner-metrics-16244
Once we've enabled it, a tarball (`kube-burner-metrics-<timestamp>.tgz`) containing all metrics will be generated in the current working directory.
This tarball can be imported and indexed by kube-burner with the import subcommand. e.g.

```shell
```log
$ kube-burner/bin/kube-burner import --config kubelet-config.yml --tarball kube-burner-metrics-1624441857.tgz
INFO[2021-06-23 11:39:40] Setting log level to info
INFO[2021-06-23 11:39:40] 📁 Creating indexer: elastic
Expand Down
62 changes: 38 additions & 24 deletions pkg/measurements/pprof.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"io"
"os"
"path"
"strings"
"sync"
"time"

Expand Down Expand Up @@ -92,16 +93,26 @@ func getPods(target types.PProftarget) []corev1.Pod {
return podList.Items
}

func (p *pprof) getPProf(wg *sync.WaitGroup, copyCerts bool) {
func (p *pprof) getPProf(wg *sync.WaitGroup, first bool) {
var err error
var command []string
for _, target := range p.config.PProfTargets {
if target.BearerToken != "" && target.Cert != "" {
log.Errorf("bearerToken and cert auth methods cannot be specified together, skipping pprof target")
continue
}
for pos, target := range p.config.PProfTargets {
log.Infof("Collecting %s pprof", target.Name)
podList := getPods(target)
for _, pod := range podList {
var cert, privKey io.Reader
if target.CertFile != "" && target.KeyFile != "" && first {
// target is a copy of one of the slice elements, so we need to modify the target object directly from the slice
p.config.PProfTargets[pos].Cert, p.config.PProfTargets[pos].Key, err = readCerts(target.CertFile, target.KeyFile)
if err != nil {
log.Error(err)
continue
}
}
if target.Cert != "" && target.Key != "" && first {
cert = strings.NewReader(target.Cert)
privKey = strings.NewReader(target.Key)
}
wg.Add(1)
go func(target types.PProftarget, pod corev1.Pod) {
defer wg.Done()
Expand All @@ -113,25 +124,14 @@ func (p *pprof) getPProf(wg *sync.WaitGroup, copyCerts bool) {
return
}
defer f.Close()
if target.Cert != "" && target.Key != "" && copyCerts {
cert, privKey, err := readCerts(target.Cert, target.Key)
if err != nil {
log.Error(err)
return
}
defer cert.Close()
defer privKey.Close()
if err != nil {
log.Error(err)
return
}
if cert != nil && privKey != nil && first {
if err = copyCertsToPod(pod, cert, privKey); err != nil {
log.Error(err)
return
}
}
if target.BearerToken != "" {
command = []string{"curl", "-sSLkH", fmt.Sprintf("Authorization: Bearer %s", target.BearerToken), target.URL}
command = []string{"curl", "-sSLkH", fmt.Sprintf("Authorization: Bearer %s", target.BearerToken), target.URL}
} else if target.Cert != "" && target.Key != "" {
command = []string{"curl", "-sSLk", "--cert", "/tmp/pprof.crt", "--key", "/tmp/pprof.key", target.URL}
} else {
Expand Down Expand Up @@ -165,7 +165,7 @@ func (p *pprof) getPProf(wg *sync.WaitGroup, copyCerts bool) {
if err != nil {
log.Errorf("Failed to get pprof from %s: %s", pod.Name, stderr.String())
}
}(target, pod)
}(p.config.PProfTargets[pos], pod)
}
}
wg.Wait()
Expand All @@ -176,17 +176,26 @@ func (p *pprof) stop() (int, error) {
return 0, nil
}

func readCerts(cert, privKey string) (*os.File, *os.File, error) {
func readCerts(cert, privKey string) (string, string, error) {
var certFd, privKeyFd *os.File
var certData, privKeyData []byte
certFd, err := os.Open(cert)
if err != nil {
return certFd, privKeyFd, fmt.Errorf("Cannot read %s, skipping: %v", cert, err)
return "", "", fmt.Errorf("Cannot read %s, skipping: %v", cert, err)
}
privKeyFd, err = os.Open(privKey)
if err != nil {
return certFd, privKeyFd, fmt.Errorf("Cannot read %s, skipping: %v", cert, err)
return "", "", fmt.Errorf("Cannot read %s, skipping: %v", cert, err)
}
return certFd, privKeyFd, nil
certData, err = io.ReadAll(certFd)
if err != nil {
return "", "", err
}
privKeyData, err = io.ReadAll(privKeyFd)
if err != nil {
return "", "", err
}
return string(certData), string(privKeyData), nil
}

func copyCertsToPod(pod corev1.Pod, cert, privKey io.Reader) error {
Expand Down Expand Up @@ -229,5 +238,10 @@ func copyCertsToPod(pod corev1.Pod, cert, privKey io.Reader) error {
}

func (p *pprof) validateConfig() error {
for _, target := range p.config.PProfTargets {
if target.BearerToken != "" && (target.CertFile != "" || target.Cert != "") {
return fmt.Errorf("bearerToken and cert auth methods cannot be specified together in the same target")
}
}
return nil
}
8 changes: 6 additions & 2 deletions pkg/measurements/types/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,12 @@ type PProftarget struct {
BearerToken string `yaml:"bearerToken"`
// URL target URL
URL string `yaml:"url"`
// Cert Client certificate file
// CertFile Client certificate file
CertFile string `yaml:"certFile"`
// KeyFile Private key file
KeyFile string `yaml:"keyFile"`
// Cert Client certificate content
Cert string `yaml:"cert"`
// Key Private key file
// Key Private key content
Key string `yaml:"key"`
}

0 comments on commit a225569

Please sign in to comment.