mirror of
https://github.com/replicatedhq/troubleshoot.git
synced 2026-02-14 18:29:53 +00:00
feat(support-bundle): add host certificate collector and analyzer (#1132)
This commit is contained in:
@@ -1671,6 +1671,55 @@ spec:
|
||||
required:
|
||||
- outcomes
|
||||
type: object
|
||||
certificatesCollection:
|
||||
properties:
|
||||
annotations:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
checkName:
|
||||
type: string
|
||||
collectorName:
|
||||
type: string
|
||||
exclude:
|
||||
type: BoolString
|
||||
outcomes:
|
||||
items:
|
||||
properties:
|
||||
fail:
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
uri:
|
||||
type: string
|
||||
when:
|
||||
type: string
|
||||
type: object
|
||||
pass:
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
uri:
|
||||
type: string
|
||||
when:
|
||||
type: string
|
||||
type: object
|
||||
warn:
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
uri:
|
||||
type: string
|
||||
when:
|
||||
type: string
|
||||
type: object
|
||||
type: object
|
||||
type: array
|
||||
strict:
|
||||
type: BoolString
|
||||
required:
|
||||
- outcomes
|
||||
type: object
|
||||
cpu:
|
||||
properties:
|
||||
annotations:
|
||||
|
||||
@@ -143,6 +143,55 @@ spec:
|
||||
required:
|
||||
- outcomes
|
||||
type: object
|
||||
certificatesCollection:
|
||||
properties:
|
||||
annotations:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
checkName:
|
||||
type: string
|
||||
collectorName:
|
||||
type: string
|
||||
exclude:
|
||||
type: BoolString
|
||||
outcomes:
|
||||
items:
|
||||
properties:
|
||||
fail:
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
uri:
|
||||
type: string
|
||||
when:
|
||||
type: string
|
||||
type: object
|
||||
pass:
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
uri:
|
||||
type: string
|
||||
when:
|
||||
type: string
|
||||
type: object
|
||||
warn:
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
uri:
|
||||
type: string
|
||||
when:
|
||||
type: string
|
||||
type: object
|
||||
type: object
|
||||
type: array
|
||||
strict:
|
||||
type: BoolString
|
||||
required:
|
||||
- outcomes
|
||||
type: object
|
||||
cpu:
|
||||
properties:
|
||||
annotations:
|
||||
@@ -1002,6 +1051,19 @@ spec:
|
||||
- certificatePath
|
||||
- keyPath
|
||||
type: object
|
||||
certificatesCollection:
|
||||
properties:
|
||||
collectorName:
|
||||
type: string
|
||||
exclude:
|
||||
type: BoolString
|
||||
paths:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
required:
|
||||
- paths
|
||||
type: object
|
||||
copy:
|
||||
properties:
|
||||
collectorName:
|
||||
|
||||
@@ -143,6 +143,55 @@ spec:
|
||||
required:
|
||||
- outcomes
|
||||
type: object
|
||||
certificatesCollection:
|
||||
properties:
|
||||
annotations:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
checkName:
|
||||
type: string
|
||||
collectorName:
|
||||
type: string
|
||||
exclude:
|
||||
type: BoolString
|
||||
outcomes:
|
||||
items:
|
||||
properties:
|
||||
fail:
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
uri:
|
||||
type: string
|
||||
when:
|
||||
type: string
|
||||
type: object
|
||||
pass:
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
uri:
|
||||
type: string
|
||||
when:
|
||||
type: string
|
||||
type: object
|
||||
warn:
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
uri:
|
||||
type: string
|
||||
when:
|
||||
type: string
|
||||
type: object
|
||||
type: object
|
||||
type: array
|
||||
strict:
|
||||
type: BoolString
|
||||
required:
|
||||
- outcomes
|
||||
type: object
|
||||
cpu:
|
||||
properties:
|
||||
annotations:
|
||||
@@ -1002,6 +1051,19 @@ spec:
|
||||
- certificatePath
|
||||
- keyPath
|
||||
type: object
|
||||
certificatesCollection:
|
||||
properties:
|
||||
collectorName:
|
||||
type: string
|
||||
exclude:
|
||||
type: BoolString
|
||||
paths:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
required:
|
||||
- paths
|
||||
type: object
|
||||
copy:
|
||||
properties:
|
||||
collectorName:
|
||||
|
||||
@@ -10403,6 +10403,55 @@ spec:
|
||||
required:
|
||||
- outcomes
|
||||
type: object
|
||||
certificatesCollection:
|
||||
properties:
|
||||
annotations:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
checkName:
|
||||
type: string
|
||||
collectorName:
|
||||
type: string
|
||||
exclude:
|
||||
type: BoolString
|
||||
outcomes:
|
||||
items:
|
||||
properties:
|
||||
fail:
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
uri:
|
||||
type: string
|
||||
when:
|
||||
type: string
|
||||
type: object
|
||||
pass:
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
uri:
|
||||
type: string
|
||||
when:
|
||||
type: string
|
||||
type: object
|
||||
warn:
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
uri:
|
||||
type: string
|
||||
when:
|
||||
type: string
|
||||
type: object
|
||||
type: object
|
||||
type: array
|
||||
strict:
|
||||
type: BoolString
|
||||
required:
|
||||
- outcomes
|
||||
type: object
|
||||
cpu:
|
||||
properties:
|
||||
annotations:
|
||||
@@ -11262,6 +11311,19 @@ spec:
|
||||
- certificatePath
|
||||
- keyPath
|
||||
type: object
|
||||
certificatesCollection:
|
||||
properties:
|
||||
collectorName:
|
||||
type: string
|
||||
exclude:
|
||||
type: BoolString
|
||||
paths:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
required:
|
||||
- paths
|
||||
type: object
|
||||
copy:
|
||||
properties:
|
||||
collectorName:
|
||||
|
||||
@@ -44,6 +44,8 @@ func GetHostAnalyzer(analyzer *troubleshootv1beta2.HostAnalyze) (HostAnalyzer, b
|
||||
return &AnalyzeHostFilesystemPerformance{analyzer.FilesystemPerformance}, true
|
||||
case analyzer.Certificate != nil:
|
||||
return &AnalyzeHostCertificate{analyzer.Certificate}, true
|
||||
case analyzer.CertificatesCollection != nil:
|
||||
return &AnalyzeHostCertificatesCollection{analyzer.CertificatesCollection}, true
|
||||
case analyzer.HostServices != nil:
|
||||
return &AnalyzeHostServices{analyzer.HostServices}, true
|
||||
case analyzer.HostOS != nil:
|
||||
|
||||
144
pkg/analyze/host_certificates_collection.go
Normal file
144
pkg/analyze/host_certificates_collection.go
Normal file
@@ -0,0 +1,144 @@
|
||||
package analyzer
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/collect"
|
||||
)
|
||||
|
||||
type AnalyzeHostCertificatesCollection struct {
|
||||
hostAnalyzer *troubleshootv1beta2.HostCertificatesCollectionAnalyze
|
||||
}
|
||||
|
||||
func (a *AnalyzeHostCertificatesCollection) Title() string {
|
||||
return hostAnalyzerTitleOrDefault(a.hostAnalyzer.AnalyzeMeta, "Host Certificates Collection")
|
||||
}
|
||||
|
||||
func (a *AnalyzeHostCertificatesCollection) IsExcluded() (bool, error) {
|
||||
return isExcluded(a.hostAnalyzer.Exclude)
|
||||
}
|
||||
|
||||
func (a *AnalyzeHostCertificatesCollection) Analyze(getCollectedFileContents func(string) ([]byte, error)) ([]*AnalyzeResult, error) {
|
||||
hostAnalyzer := a.hostAnalyzer
|
||||
|
||||
collectorName := hostAnalyzer.CollectorName
|
||||
if collectorName == "" {
|
||||
collectorName = "certificatesCollection"
|
||||
}
|
||||
name := filepath.Join("host-collectors/certificatesCollection", collectorName+".json")
|
||||
|
||||
certificatesInfo, err := getCollectedFileContents(name)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get contents of certificatesCollection.json")
|
||||
}
|
||||
|
||||
collectorCertificates := []collect.HostCertificatesCollection{}
|
||||
if err := json.Unmarshal(certificatesInfo, &collectorCertificates); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to parse certificatesCollection.json")
|
||||
}
|
||||
|
||||
var coll resultCollector
|
||||
|
||||
for _, cert := range collectorCertificates {
|
||||
|
||||
source := ""
|
||||
|
||||
if cert.CertificatePath != "" {
|
||||
source = fmt.Sprintf("obtained from %s", cert.CertificatePath)
|
||||
}
|
||||
|
||||
if cert.Message == collect.CertMissing {
|
||||
// return the result immediately if the certificate is missing
|
||||
coll.push(&AnalyzeResult{
|
||||
Title: a.Title(),
|
||||
IsFail: true,
|
||||
Message: fmt.Sprintf("Certificate is missing, cannot be %s", source),
|
||||
})
|
||||
} else {
|
||||
results, err := a.analyzeHostAnalyzeCertificatesResult(cert.CertificateChain, hostAnalyzer.Outcomes, source)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, result := range results {
|
||||
coll.push(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return coll.get(a.Title()), nil
|
||||
}
|
||||
|
||||
func (a *AnalyzeHostCertificatesCollection) analyzeHostAnalyzeCertificatesResult(certificateChains []collect.ParsedCertificate, outcomes []*troubleshootv1beta2.Outcome, source string) ([]*AnalyzeResult, error) {
|
||||
var coll resultCollector
|
||||
var passResults []*AnalyzeResult
|
||||
when := ""
|
||||
message := ""
|
||||
|
||||
for _, certChain := range certificateChains {
|
||||
for _, outcome := range outcomes {
|
||||
result := &AnalyzeResult{
|
||||
Title: a.Title(),
|
||||
}
|
||||
|
||||
if outcome.Fail != nil {
|
||||
result.IsFail = true
|
||||
when = outcome.Fail.When
|
||||
message = outcome.Fail.Message
|
||||
} else if outcome.Warn != nil {
|
||||
result.IsWarn = true
|
||||
when = outcome.Warn.When
|
||||
message = outcome.Warn.Message
|
||||
} else if outcome.Pass != nil {
|
||||
result.IsPass = true
|
||||
when = outcome.Pass.When
|
||||
message = outcome.Pass.Message
|
||||
} else {
|
||||
return nil, errors.New("empty outcome")
|
||||
}
|
||||
|
||||
if result.IsPass && certChain.IsValid {
|
||||
result.Message = fmt.Sprintf("%s, %s", message, source)
|
||||
// if the certificate is valid, we need to wait for the warning check whether the certificate is going to expire
|
||||
passResults = append(passResults, result)
|
||||
}
|
||||
|
||||
if result.IsFail && !certChain.IsValid {
|
||||
result.Message = fmt.Sprintf("%s, %s", message, source)
|
||||
// return the result immediately if the certificate is invalid
|
||||
coll.push(result)
|
||||
}
|
||||
|
||||
if result.IsWarn && certChain.IsValid {
|
||||
warnDate, _ := regexp.Compile(`notAfter \< Today \+ (\d+) days`)
|
||||
warnMatch := warnDate.FindStringSubmatch(when)
|
||||
if warnMatch != nil {
|
||||
warnMatchDays, err := strconv.Atoi(warnMatch[1])
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to convert string to integer")
|
||||
}
|
||||
|
||||
targetTime := time.Now().AddDate(0, 0, warnMatchDays)
|
||||
|
||||
if targetTime.After(certChain.NotAfter) {
|
||||
result.Message = fmt.Sprintf("%s in %d days, %s", message, warnMatchDays, source)
|
||||
// discard passResults if the certificate is going to expire in certain days
|
||||
passResults = []*AnalyzeResult{}
|
||||
coll.push(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// append passResults if the certificate is valid and not going to expire in certain days
|
||||
for _, passResult := range passResults {
|
||||
coll.push(passResult)
|
||||
}
|
||||
}
|
||||
return coll.results, nil
|
||||
}
|
||||
221
pkg/analyze/host_certificates_collection_test.go
Normal file
221
pkg/analyze/host_certificates_collection_test.go
Normal file
@@ -0,0 +1,221 @@
|
||||
package analyzer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestAnalyzeHostCertificatesCollection(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
file string
|
||||
hostAnalyzer *troubleshootv1beta2.HostCertificatesCollectionAnalyze
|
||||
result []*AnalyzeResult
|
||||
expectErr bool
|
||||
}{
|
||||
{
|
||||
name: "certificate-valid",
|
||||
file: fmt.Sprintf(`[{
|
||||
"certificatePath": "apiserver-kubelet-client.crt",
|
||||
"certificateChain": [
|
||||
{
|
||||
"certificate": "ca.crt",
|
||||
"subject": "CN=kubernetes",
|
||||
"subjectAlternativeNames": [
|
||||
"kubernetes"
|
||||
],
|
||||
"issuer": "CN=kubernetes",
|
||||
"notAfter": "%s",
|
||||
"notBefore": "2023-04-19T00:30:20Z",
|
||||
"isValid": true,
|
||||
"isCA": true
|
||||
}
|
||||
],
|
||||
"message": "cert-valid"
|
||||
}]`, time.Now().AddDate(1, 0, 0).Format("2006-01-02T15:04:05Z")),
|
||||
hostAnalyzer: &troubleshootv1beta2.HostCertificatesCollectionAnalyze{
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Pass: &troubleshootv1beta2.SingleOutcome{
|
||||
Message: "Certificate is valid",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
result: []*AnalyzeResult{
|
||||
{
|
||||
IsPass: true,
|
||||
IsWarn: false,
|
||||
IsFail: false,
|
||||
Title: "Host Certificates Collection",
|
||||
Message: "Certificate is valid, obtained from apiserver-kubelet-client.crt",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "certificate-invalid",
|
||||
file: `[{
|
||||
"certificatePath": "apiserver-kubelet-client.crt",
|
||||
"certificateChain": [
|
||||
{
|
||||
"certificate": "ca.crt",
|
||||
"subject": "CN=kubernetes",
|
||||
"subjectAlternativeNames": [
|
||||
"kubernetes"
|
||||
],
|
||||
"issuer": "CN=kubernetes",
|
||||
"notAfter": "2022-04-16T00:30:20Z",
|
||||
"notBefore": "2021-04-19T00:30:20Z",
|
||||
"isValid": false,
|
||||
"isCA": true
|
||||
}
|
||||
],
|
||||
"message": "cert-invalid"
|
||||
}]`,
|
||||
hostAnalyzer: &troubleshootv1beta2.HostCertificatesCollectionAnalyze{
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "notAfter < Today",
|
||||
Message: "Certificate has expired",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
result: []*AnalyzeResult{
|
||||
{
|
||||
IsPass: false,
|
||||
IsWarn: false,
|
||||
IsFail: true,
|
||||
Title: "Host Certificates Collection",
|
||||
Message: "Certificate has expired, obtained from apiserver-kubelet-client.crt",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "certificate-about-to-expire",
|
||||
file: fmt.Sprintf(`[{
|
||||
"certificatePath": "apiserver-kubelet-client.crt",
|
||||
"certificateChain": [
|
||||
{
|
||||
"certificate": "ca.crt",
|
||||
"subject": "CN=kubernetes",
|
||||
"subjectAlternativeNames": [
|
||||
"kubernetes"
|
||||
],
|
||||
"issuer": "CN=kubernetes",
|
||||
"notAfter": "%s",
|
||||
"notBefore": "2021-04-19T00:30:20Z",
|
||||
"isValid": true,
|
||||
"isCA": true
|
||||
}
|
||||
],
|
||||
"message": "cert-valid"
|
||||
}]`, time.Now().AddDate(0, 0, 5).Format("2006-01-02T15:04:05Z")),
|
||||
hostAnalyzer: &troubleshootv1beta2.HostCertificatesCollectionAnalyze{
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Warn: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "notAfter < Today + 15 days",
|
||||
Message: "Certificate is about to expire",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
result: []*AnalyzeResult{
|
||||
{
|
||||
IsPass: false,
|
||||
IsWarn: true,
|
||||
IsFail: false,
|
||||
Title: "Host Certificates Collection",
|
||||
Message: "Certificate is about to expire in 15 days, obtained from apiserver-kubelet-client.crt",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "certificate-missing",
|
||||
file: `[{
|
||||
"certificatePath": "apiserver-kubelet-client.crt",
|
||||
"message": "cert-missing"
|
||||
}]`,
|
||||
hostAnalyzer: &troubleshootv1beta2.HostCertificatesCollectionAnalyze{
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{},
|
||||
},
|
||||
result: []*AnalyzeResult{
|
||||
{
|
||||
IsPass: false,
|
||||
IsWarn: false,
|
||||
IsFail: true,
|
||||
Title: "Host Certificates Collection",
|
||||
Message: "Certificate is missing, cannot be obtained from apiserver-kubelet-client.crt",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "certificate-valid-and-about-to-expire",
|
||||
file: fmt.Sprintf(`[{
|
||||
"certificatePath": "apiserver-kubelet-client.crt",
|
||||
"certificateChain": [
|
||||
{
|
||||
"certificate": "ca.crt",
|
||||
"subject": "CN=kubernetes",
|
||||
"subjectAlternativeNames": [
|
||||
"kubernetes"
|
||||
],
|
||||
"issuer": "CN=kubernetes",
|
||||
"notAfter": "%s",
|
||||
"notBefore": "2021-04-19T00:30:20Z",
|
||||
"isValid": true,
|
||||
"isCA": true
|
||||
}
|
||||
],
|
||||
"message": "cert-valid"
|
||||
}]`, time.Now().AddDate(0, 0, 5).Format("2006-01-02T15:04:05Z")),
|
||||
hostAnalyzer: &troubleshootv1beta2.HostCertificatesCollectionAnalyze{
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Pass: &troubleshootv1beta2.SingleOutcome{
|
||||
Message: "Certificate is valid",
|
||||
},
|
||||
Warn: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "notAfter < Today + 15 days",
|
||||
Message: "Certificate is about to expire",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
result: []*AnalyzeResult{
|
||||
{
|
||||
IsPass: false,
|
||||
IsWarn: true,
|
||||
IsFail: false,
|
||||
Title: "Host Certificates Collection",
|
||||
Message: "Certificate is about to expire in 15 days, obtained from apiserver-kubelet-client.crt",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
req := require.New(t)
|
||||
|
||||
getCollectedFileContents := func(filename string) ([]byte, error) {
|
||||
return []byte(test.file), nil
|
||||
}
|
||||
|
||||
result, err := (&AnalyzeHostCertificatesCollection{test.hostAnalyzer}).Analyze(getCollectedFileContents)
|
||||
if test.expectErr {
|
||||
req.Error(err)
|
||||
} else {
|
||||
req.NoError(err)
|
||||
}
|
||||
|
||||
assert.Equal(t, test.result, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -104,6 +104,12 @@ type CertificateAnalyze struct {
|
||||
Outcomes []*Outcome `json:"outcomes" yaml:"outcomes"`
|
||||
}
|
||||
|
||||
type HostCertificatesCollectionAnalyze struct {
|
||||
AnalyzeMeta `json:",inline" yaml:",inline"`
|
||||
CollectorName string `json:"collectorName,omitempty" yaml:"collectorName,omitempty"`
|
||||
Outcomes []*Outcome `json:"outcomes" yaml:"outcomes"`
|
||||
}
|
||||
|
||||
type HostServicesAnalyze struct {
|
||||
AnalyzeMeta `json:",inline" yaml:",inline"`
|
||||
CollectorName string `json:"collectorName,omitempty" yaml:"collectorName,omitempty"`
|
||||
@@ -149,6 +155,8 @@ type HostAnalyze struct {
|
||||
|
||||
Certificate *CertificateAnalyze `json:"certificate,omitempty" yaml:"certificate,omitempty"`
|
||||
|
||||
CertificatesCollection *HostCertificatesCollectionAnalyze `json:"certificatesCollection,omitempty" yaml:"certificatesCollection,omitempty"`
|
||||
|
||||
HostServices *HostServicesAnalyze `json:"hostServices,omitempty" yaml:"hostServices,omitempty"`
|
||||
|
||||
HostOS *HostOSAnalyze `json:"hostOS,omitempty" yaml:"hostOS,omitempty"`
|
||||
|
||||
@@ -169,6 +169,11 @@ type Certificate struct {
|
||||
KeyPath string `json:"keyPath" yaml:"keyPath"`
|
||||
}
|
||||
|
||||
type HostCertificatesCollection struct {
|
||||
HostCollectorMeta `json:",inline" yaml:",inline"`
|
||||
Paths []string `json:"paths" yaml:"paths"`
|
||||
}
|
||||
|
||||
type HostServices struct {
|
||||
HostCollectorMeta `json:",inline" yaml:",inline"`
|
||||
}
|
||||
@@ -180,28 +185,29 @@ type HostRun struct {
|
||||
}
|
||||
|
||||
type HostCollect struct {
|
||||
CPU *CPU `json:"cpu,omitempty" yaml:"cpu,omitempty"`
|
||||
Memory *Memory `json:"memory,omitempty" yaml:"memory,omitempty"`
|
||||
TCPLoadBalancer *TCPLoadBalancer `json:"tcpLoadBalancer,omitempty" yaml:"tcpLoadBalancer,omitempty"`
|
||||
HTTPLoadBalancer *HTTPLoadBalancer `json:"httpLoadBalancer,omitempty" yaml:"httpLoadBalancer,omitempty"`
|
||||
TCPPortStatus *TCPPortStatus `json:"tcpPortStatus,omitempty" yaml:"tcpPortStatus,omitempty"`
|
||||
UDPPortStatus *UDPPortStatus `json:"udpPortStatus,omitempty" yaml:"udpPortStatus,omitempty"`
|
||||
Kubernetes *Kubernetes `json:"kubernetes,omitempty" yaml:"kubernetes,omitempty"`
|
||||
IPV4Interfaces *IPV4Interfaces `json:"ipv4Interfaces,omitempty" yaml:"ipv4Interfaces,omitempty"`
|
||||
SubnetAvailable *SubnetAvailable `json:"subnetAvailable,omitempty" yaml:"subnetAvailable,omitempty"`
|
||||
DiskUsage *DiskUsage `json:"diskUsage,omitempty" yaml:"diskUsage,omitempty"`
|
||||
HTTP *HostHTTP `json:"http,omitempty" yaml:"http,omitempty"`
|
||||
Time *HostTime `json:"time,omitempty" yaml:"time,omitempty"`
|
||||
BlockDevices *HostBlockDevices `json:"blockDevices,omitempty" yaml:"blockDevices,omitempty"`
|
||||
SystemPackages *HostSystemPackages `json:"systemPackages,omitempty" yaml:"systemPackages,omitempty"`
|
||||
KernelModules *HostKernelModules `json:"kernelModules,omitempty" yaml:"kernelModules,omitempty"`
|
||||
TCPConnect *TCPConnect `json:"tcpConnect,omitempty" yaml:"tcpConnect,omitempty"`
|
||||
FilesystemPerformance *FilesystemPerformance `json:"filesystemPerformance,omitempty" yaml:"filesystemPerformance,omitempty"`
|
||||
Certificate *Certificate `json:"certificate,omitempty" yaml:"certificate,omitempty"`
|
||||
HostServices *HostServices `json:"hostServices,omitempty" yaml:"hostServices,omitempty"`
|
||||
HostOS *HostOS `json:"hostOS,omitempty" yaml:"hostOS,omitempty"`
|
||||
HostRun *HostRun `json:"run,omitempty" yaml:"run,omitempty"`
|
||||
HostCopy *HostCopy `json:"copy,omitempty" yaml:"copy,omitempty"`
|
||||
CPU *CPU `json:"cpu,omitempty" yaml:"cpu,omitempty"`
|
||||
Memory *Memory `json:"memory,omitempty" yaml:"memory,omitempty"`
|
||||
TCPLoadBalancer *TCPLoadBalancer `json:"tcpLoadBalancer,omitempty" yaml:"tcpLoadBalancer,omitempty"`
|
||||
HTTPLoadBalancer *HTTPLoadBalancer `json:"httpLoadBalancer,omitempty" yaml:"httpLoadBalancer,omitempty"`
|
||||
TCPPortStatus *TCPPortStatus `json:"tcpPortStatus,omitempty" yaml:"tcpPortStatus,omitempty"`
|
||||
UDPPortStatus *UDPPortStatus `json:"udpPortStatus,omitempty" yaml:"udpPortStatus,omitempty"`
|
||||
Kubernetes *Kubernetes `json:"kubernetes,omitempty" yaml:"kubernetes,omitempty"`
|
||||
IPV4Interfaces *IPV4Interfaces `json:"ipv4Interfaces,omitempty" yaml:"ipv4Interfaces,omitempty"`
|
||||
SubnetAvailable *SubnetAvailable `json:"subnetAvailable,omitempty" yaml:"subnetAvailable,omitempty"`
|
||||
DiskUsage *DiskUsage `json:"diskUsage,omitempty" yaml:"diskUsage,omitempty"`
|
||||
HTTP *HostHTTP `json:"http,omitempty" yaml:"http,omitempty"`
|
||||
Time *HostTime `json:"time,omitempty" yaml:"time,omitempty"`
|
||||
BlockDevices *HostBlockDevices `json:"blockDevices,omitempty" yaml:"blockDevices,omitempty"`
|
||||
SystemPackages *HostSystemPackages `json:"systemPackages,omitempty" yaml:"systemPackages,omitempty"`
|
||||
KernelModules *HostKernelModules `json:"kernelModules,omitempty" yaml:"kernelModules,omitempty"`
|
||||
TCPConnect *TCPConnect `json:"tcpConnect,omitempty" yaml:"tcpConnect,omitempty"`
|
||||
FilesystemPerformance *FilesystemPerformance `json:"filesystemPerformance,omitempty" yaml:"filesystemPerformance,omitempty"`
|
||||
Certificate *Certificate `json:"certificate,omitempty" yaml:"certificate,omitempty"`
|
||||
CertificatesCollection *HostCertificatesCollection `json:"certificatesCollection,omitempty" yaml:"certificatesCollection,omitempty"`
|
||||
HostServices *HostServices `json:"hostServices,omitempty" yaml:"hostServices,omitempty"`
|
||||
HostOS *HostOS `json:"hostOS,omitempty" yaml:"hostOS,omitempty"`
|
||||
HostRun *HostRun `json:"run,omitempty" yaml:"run,omitempty"`
|
||||
HostCopy *HostCopy `json:"copy,omitempty" yaml:"copy,omitempty"`
|
||||
}
|
||||
|
||||
func (c *HostCollect) GetName() string {
|
||||
|
||||
@@ -1687,6 +1687,11 @@ func (in *HostAnalyze) DeepCopyInto(out *HostAnalyze) {
|
||||
*out = new(CertificateAnalyze)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.CertificatesCollection != nil {
|
||||
in, out := &in.CertificatesCollection, &out.CertificatesCollection
|
||||
*out = new(HostCertificatesCollectionAnalyze)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.HostServices != nil {
|
||||
in, out := &in.HostServices, &out.HostServices
|
||||
*out = new(HostServicesAnalyze)
|
||||
@@ -1725,6 +1730,54 @@ func (in *HostBlockDevices) DeepCopy() *HostBlockDevices {
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *HostCertificatesCollection) DeepCopyInto(out *HostCertificatesCollection) {
|
||||
*out = *in
|
||||
in.HostCollectorMeta.DeepCopyInto(&out.HostCollectorMeta)
|
||||
if in.Paths != nil {
|
||||
in, out := &in.Paths, &out.Paths
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HostCertificatesCollection.
|
||||
func (in *HostCertificatesCollection) DeepCopy() *HostCertificatesCollection {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(HostCertificatesCollection)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *HostCertificatesCollectionAnalyze) DeepCopyInto(out *HostCertificatesCollectionAnalyze) {
|
||||
*out = *in
|
||||
in.AnalyzeMeta.DeepCopyInto(&out.AnalyzeMeta)
|
||||
if in.Outcomes != nil {
|
||||
in, out := &in.Outcomes, &out.Outcomes
|
||||
*out = make([]*Outcome, len(*in))
|
||||
for i := range *in {
|
||||
if (*in)[i] != nil {
|
||||
in, out := &(*in)[i], &(*out)[i]
|
||||
*out = new(Outcome)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HostCertificatesCollectionAnalyze.
|
||||
func (in *HostCertificatesCollectionAnalyze) DeepCopy() *HostCertificatesCollectionAnalyze {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(HostCertificatesCollectionAnalyze)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *HostCollect) DeepCopyInto(out *HostCollect) {
|
||||
*out = *in
|
||||
@@ -1818,6 +1871,11 @@ func (in *HostCollect) DeepCopyInto(out *HostCollect) {
|
||||
*out = new(Certificate)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.CertificatesCollection != nil {
|
||||
in, out := &in.CertificatesCollection, &out.CertificatesCollection
|
||||
*out = new(HostCertificatesCollection)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.HostServices != nil {
|
||||
in, out := &in.HostServices, &out.HostServices
|
||||
*out = new(HostServices)
|
||||
|
||||
@@ -3,10 +3,8 @@ package collect
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"time"
|
||||
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
@@ -194,27 +192,6 @@ func secretCertCollector(secretName string, namespace string, client kubernetes.
|
||||
return results
|
||||
}
|
||||
|
||||
// decode pem and validate data source contains
|
||||
func decodePem(certPEMBlock []byte) (tls.Certificate, string) {
|
||||
var cert tls.Certificate
|
||||
var trackErrors string
|
||||
var certDERBlock *pem.Block
|
||||
|
||||
for {
|
||||
certDERBlock, certPEMBlock = pem.Decode(certPEMBlock)
|
||||
if certDERBlock == nil {
|
||||
break
|
||||
}
|
||||
if certDERBlock.Type == "CERTIFICATE" {
|
||||
cert.Certificate = append(cert.Certificate, certDERBlock.Bytes)
|
||||
}
|
||||
}
|
||||
if len(cert.Certificate) == 0 {
|
||||
trackErrors = "No certificates found in"
|
||||
}
|
||||
return cert, trackErrors
|
||||
}
|
||||
|
||||
// Certificate parser
|
||||
func CertParser(certName string, certs []byte, currentTime time.Time) ([]ParsedCertificate, []string) {
|
||||
certInfo := []ParsedCertificate{}
|
||||
|
||||
106
pkg/collect/host_certificates_collection.go
Normal file
106
pkg/collect/host_certificates_collection.go
Normal file
@@ -0,0 +1,106 @@
|
||||
package collect
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
)
|
||||
|
||||
const CertMissing = "cert-missing"
|
||||
const CertValid = "cert-valid"
|
||||
const CertInvalid = "cert-invalid"
|
||||
|
||||
type CollectHostCertificatesCollection struct {
|
||||
hostCollector *troubleshootv1beta2.HostCertificatesCollection
|
||||
BundlePath string
|
||||
}
|
||||
|
||||
type HostCertificatesCollection struct {
|
||||
CertificatePath string `json:"certificatePath,omitempty"`
|
||||
CertificateChain []ParsedCertificate `json:"certificateChain,omitempty"`
|
||||
Message string `json:"message,omitempty"`
|
||||
}
|
||||
|
||||
func (c *CollectHostCertificatesCollection) Title() string {
|
||||
return hostCollectorTitleOrDefault(c.hostCollector.HostCollectorMeta, "Host Certificates Collection")
|
||||
}
|
||||
|
||||
func (c *CollectHostCertificatesCollection) IsExcluded() (bool, error) {
|
||||
return isExcluded(c.hostCollector.Exclude)
|
||||
}
|
||||
|
||||
func (c *CollectHostCertificatesCollection) Collect(progressChan chan<- interface{}) (map[string][]byte, error) {
|
||||
var results []HostCertificatesCollection
|
||||
|
||||
for _, certPath := range c.hostCollector.Paths {
|
||||
results = append(results, HostCertsParser(certPath))
|
||||
}
|
||||
|
||||
resultsJson, errResultJson := json.MarshalIndent(results, "", "\t")
|
||||
if errResultJson != nil {
|
||||
return nil, errResultJson
|
||||
}
|
||||
|
||||
collectorName := c.hostCollector.CollectorName
|
||||
if collectorName == "" {
|
||||
collectorName = "certificatesCollection"
|
||||
}
|
||||
name := filepath.Join("host-collectors/certificatesCollection", collectorName+".json")
|
||||
|
||||
output := NewResult()
|
||||
output.SaveResult(c.BundlePath, name, bytes.NewBuffer(resultsJson))
|
||||
|
||||
return output, nil
|
||||
}
|
||||
|
||||
func HostCertsParser(certPath string) HostCertificatesCollection {
|
||||
var certInfo []ParsedCertificate
|
||||
|
||||
cert, err := ioutil.ReadFile(certPath)
|
||||
if err != nil {
|
||||
return HostCertificatesCollection{
|
||||
CertificatePath: certPath,
|
||||
Message: CertMissing,
|
||||
}
|
||||
}
|
||||
|
||||
certChain, _ := decodePem(cert)
|
||||
|
||||
if len(certChain.Certificate) == 0 {
|
||||
return HostCertificatesCollection{
|
||||
CertificatePath: certPath,
|
||||
Message: CertInvalid,
|
||||
}
|
||||
}
|
||||
|
||||
for _, cert := range certChain.Certificate {
|
||||
parsedCert, errParse := x509.ParseCertificate(cert)
|
||||
if errParse != nil {
|
||||
return HostCertificatesCollection{
|
||||
CertificatePath: certPath,
|
||||
Message: CertInvalid,
|
||||
}
|
||||
}
|
||||
currentTime := time.Now()
|
||||
certInfo = append(certInfo, ParsedCertificate{
|
||||
Subject: parsedCert.Subject.ToRDNSequence().String(),
|
||||
SubjectAlternativeNames: parsedCert.DNSNames,
|
||||
Issuer: parsedCert.Issuer.ToRDNSequence().String(),
|
||||
NotAfter: parsedCert.NotAfter,
|
||||
NotBefore: parsedCert.NotBefore,
|
||||
IsValid: currentTime.Before(parsedCert.NotAfter) && currentTime.After(parsedCert.NotBefore),
|
||||
IsCA: parsedCert.IsCA,
|
||||
})
|
||||
}
|
||||
|
||||
return HostCertificatesCollection{
|
||||
CertificatePath: certPath,
|
||||
CertificateChain: certInfo,
|
||||
Message: CertValid,
|
||||
}
|
||||
}
|
||||
82
pkg/collect/host_certificates_collection_test.go
Normal file
82
pkg/collect/host_certificates_collection_test.go
Normal file
@@ -0,0 +1,82 @@
|
||||
package collect
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/replicatedhq/troubleshoot/internal/testutils"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_HostCertParser(t *testing.T) {
|
||||
path := filepath.Join(t.TempDir(), "test.txt")
|
||||
tests := []struct {
|
||||
name string
|
||||
filePath, certChain string
|
||||
want HostCertificatesCollection
|
||||
}{
|
||||
{
|
||||
name: "valid certificate",
|
||||
filePath: path,
|
||||
certChain: certChains["validCert"],
|
||||
want: HostCertificatesCollection{
|
||||
CertificatePath: path,
|
||||
CertificateChain: []ParsedCertificate{
|
||||
{
|
||||
Subject: "CN=envoy",
|
||||
SubjectAlternativeNames: []string{
|
||||
"envoy",
|
||||
"envoy.projectcontour",
|
||||
"envoy.projectcontour.svc",
|
||||
"envoy.projectcontour.svc.cluster.local",
|
||||
},
|
||||
Issuer: "SERIALNUMBER=615929891,CN=Project Contour",
|
||||
NotAfter: time.Date(2024, time.February, 25, 4, 27, 16, 0, time.UTC),
|
||||
NotBefore: time.Date(2023, time.February, 24, 4, 27, 18, 0, time.UTC),
|
||||
IsValid: true,
|
||||
IsCA: false,
|
||||
},
|
||||
},
|
||||
Message: "cert-valid",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "expired certificate",
|
||||
filePath: path,
|
||||
certChain: certChains["expiredCert"],
|
||||
want: HostCertificatesCollection{
|
||||
CertificatePath: path,
|
||||
CertificateChain: []ParsedCertificate{
|
||||
{
|
||||
Subject: "O=Internet Widgits Pty Ltd,ST=Some-State,C=AU",
|
||||
SubjectAlternativeNames: nil,
|
||||
Issuer: "O=Internet Widgits Pty Ltd,ST=Some-State,C=AU",
|
||||
NotAfter: time.Date(2015, time.September, 12, 21, 52, 2, 0, time.UTC),
|
||||
NotBefore: time.Date(2012, time.September, 12, 21, 52, 2, 0, time.UTC),
|
||||
IsValid: false,
|
||||
IsCA: true,
|
||||
},
|
||||
},
|
||||
Message: "cert-valid",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "missing certificate",
|
||||
filePath: "",
|
||||
certChain: "",
|
||||
want: HostCertificatesCollection{
|
||||
CertificatePath: "",
|
||||
Message: "cert-missing",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
testutils.CreateTestFileWithData(t, path, tt.certChain)
|
||||
got := HostCertsParser(tt.filePath)
|
||||
assert.Equal(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -51,6 +51,8 @@ func GetHostCollector(collector *troubleshootv1beta2.HostCollect, bundlePath str
|
||||
return &CollectHostFilesystemPerformance{collector.FilesystemPerformance, bundlePath}, true
|
||||
case collector.Certificate != nil:
|
||||
return &CollectHostCertificate{collector.Certificate, bundlePath}, true
|
||||
case collector.CertificatesCollection != nil:
|
||||
return &CollectHostCertificatesCollection{collector.CertificatesCollection, bundlePath}, true
|
||||
case collector.HostServices != nil:
|
||||
return &CollectHostServices{collector.HostServices, bundlePath}, true
|
||||
case collector.HostOS != nil:
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
@@ -236,3 +237,24 @@ func checkForExistingServiceAccount(client kubernetes.Interface, namespace strin
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// decode pem and validate certificate data source contains
|
||||
func decodePem(certPEMBlock []byte) (tls.Certificate, string) {
|
||||
var cert tls.Certificate
|
||||
var trackErrors string
|
||||
var certDERBlock *pem.Block
|
||||
|
||||
for {
|
||||
certDERBlock, certPEMBlock = pem.Decode(certPEMBlock)
|
||||
if certDERBlock == nil {
|
||||
break
|
||||
}
|
||||
if certDERBlock.Type == "CERTIFICATE" {
|
||||
cert.Certificate = append(cert.Certificate, certDERBlock.Bytes)
|
||||
}
|
||||
}
|
||||
if len(cert.Certificate) == 0 {
|
||||
trackErrors = "No certificates found in"
|
||||
}
|
||||
return cert, trackErrors
|
||||
}
|
||||
|
||||
@@ -2535,6 +2535,82 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"certificatesCollection": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"outcomes"
|
||||
],
|
||||
"properties": {
|
||||
"annotations": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"checkName": {
|
||||
"type": "string"
|
||||
},
|
||||
"collectorName": {
|
||||
"type": "string"
|
||||
},
|
||||
"exclude": {
|
||||
"oneOf": [{"type": "string"},{"type": "boolean"}]
|
||||
},
|
||||
"outcomes": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"fail": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"uri": {
|
||||
"type": "string"
|
||||
},
|
||||
"when": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"pass": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"uri": {
|
||||
"type": "string"
|
||||
},
|
||||
"when": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"warn": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"uri": {
|
||||
"type": "string"
|
||||
},
|
||||
"when": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"strict": {
|
||||
"oneOf": [{"type": "string"},{"type": "boolean"}]
|
||||
}
|
||||
}
|
||||
},
|
||||
"cpu": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
|
||||
@@ -9588,6 +9588,82 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"certificatesCollection": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"outcomes"
|
||||
],
|
||||
"properties": {
|
||||
"annotations": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"checkName": {
|
||||
"type": "string"
|
||||
},
|
||||
"collectorName": {
|
||||
"type": "string"
|
||||
},
|
||||
"exclude": {
|
||||
"oneOf": [{"type": "string"},{"type": "boolean"}]
|
||||
},
|
||||
"outcomes": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"fail": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"uri": {
|
||||
"type": "string"
|
||||
},
|
||||
"when": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"pass": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"uri": {
|
||||
"type": "string"
|
||||
},
|
||||
"when": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"warn": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"uri": {
|
||||
"type": "string"
|
||||
},
|
||||
"when": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"strict": {
|
||||
"oneOf": [{"type": "string"},{"type": "boolean"}]
|
||||
}
|
||||
}
|
||||
},
|
||||
"cpu": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
@@ -10920,6 +10996,26 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"certificatesCollection": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"paths"
|
||||
],
|
||||
"properties": {
|
||||
"collectorName": {
|
||||
"type": "string"
|
||||
},
|
||||
"exclude": {
|
||||
"oneOf": [{"type": "string"},{"type": "boolean"}]
|
||||
},
|
||||
"paths": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"copy": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
|
||||
Reference in New Issue
Block a user