diff --git a/cmd/collect/cli/root.go b/cmd/collect/cli/root.go index 86fdc5c63..be6962390 100644 --- a/cmd/collect/cli/root.go +++ b/cmd/collect/cli/root.go @@ -60,7 +60,7 @@ func RootCmd() *cobra.Command { cmd.Flags().String("chroot", "", "Chroot to path") // hidden in favor of the `insecure-skip-tls-verify` flag - cmd.Flags().Bool("allow-insecure-connections", false, "when set, do not verify TLS certs when retrieving spec and reporting results") + cmd.Flags().Bool("allow-insecure-connections", false, "when set, do not verify TLSCertificate certs when retrieving spec and reporting results") cmd.Flags().MarkHidden("allow-insecure-connections") viper.BindPFlags(cmd.Flags()) diff --git a/cmd/troubleshoot/cli/root.go b/cmd/troubleshoot/cli/root.go index f2cd03901..185c23d10 100644 --- a/cmd/troubleshoot/cli/root.go +++ b/cmd/troubleshoot/cli/root.go @@ -97,7 +97,7 @@ If no arguments are provided, specs are automatically loaded from the cluster by cmd.Flags().Bool("dry-run", false, "print support bundle spec without collecting anything") // hidden in favor of the `insecure-skip-tls-verify` flag - cmd.Flags().Bool("allow-insecure-connections", false, "when set, do not verify TLS certs when retrieving spec and reporting results") + cmd.Flags().Bool("allow-insecure-connections", false, "when set, do not verify TLSCertificate certs when retrieving spec and reporting results") cmd.Flags().MarkHidden("allow-insecure-connections") // `no-uri` references the `followURI` functionality where we can use an upstream spec when creating a support bundle diff --git a/config/crds/troubleshoot.sh_analyzers.yaml b/config/crds/troubleshoot.sh_analyzers.yaml index 5599be775..235dd6f65 100644 --- a/config/crds/troubleshoot.sh_analyzers.yaml +++ b/config/crds/troubleshoot.sh_analyzers.yaml @@ -3156,6 +3156,55 @@ spec: required: - outcomes type: object + tlsCertificate: + 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 udpPortStatus: properties: annotations: diff --git a/config/crds/troubleshoot.sh_collectors.yaml b/config/crds/troubleshoot.sh_collectors.yaml index 292e2196b..56c3b80da 100644 --- a/config/crds/troubleshoot.sh_collectors.yaml +++ b/config/crds/troubleshoot.sh_collectors.yaml @@ -17744,6 +17744,21 @@ spec: exclude: type: BoolString type: object + tlsCertificate: + properties: + address: + type: string + collectorName: + type: string + exclude: + type: BoolString + expectedCertSubpath: + type: string + httpsProxy: + type: string + required: + - address + type: object udpPortStatus: properties: collectorName: diff --git a/config/crds/troubleshoot.sh_hostcollectors.yaml b/config/crds/troubleshoot.sh_hostcollectors.yaml index 21843dec3..256aaa793 100644 --- a/config/crds/troubleshoot.sh_hostcollectors.yaml +++ b/config/crds/troubleshoot.sh_hostcollectors.yaml @@ -1305,6 +1305,55 @@ spec: required: - outcomes type: object + tlsCertificate: + 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 udpPortStatus: properties: annotations: @@ -1970,6 +2019,21 @@ spec: exclude: type: BoolString type: object + tlsCertificate: + properties: + address: + type: string + collectorName: + type: string + exclude: + type: BoolString + expectedCertSubpath: + type: string + httpsProxy: + type: string + required: + - address + type: object udpPortStatus: properties: collectorName: diff --git a/config/crds/troubleshoot.sh_hostpreflights.yaml b/config/crds/troubleshoot.sh_hostpreflights.yaml index bd1cb99a6..b53abd982 100644 --- a/config/crds/troubleshoot.sh_hostpreflights.yaml +++ b/config/crds/troubleshoot.sh_hostpreflights.yaml @@ -1305,6 +1305,55 @@ spec: required: - outcomes type: object + tlsCertificate: + 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 udpPortStatus: properties: annotations: @@ -1970,6 +2019,21 @@ spec: exclude: type: BoolString type: object + tlsCertificate: + properties: + address: + type: string + collectorName: + type: string + exclude: + type: BoolString + expectedCertSubpath: + type: string + httpsProxy: + type: string + required: + - address + type: object udpPortStatus: properties: collectorName: diff --git a/config/crds/troubleshoot.sh_supportbundles.yaml b/config/crds/troubleshoot.sh_supportbundles.yaml index f00a2ec0c..84ba3623a 100644 --- a/config/crds/troubleshoot.sh_supportbundles.yaml +++ b/config/crds/troubleshoot.sh_supportbundles.yaml @@ -20247,6 +20247,55 @@ spec: required: - outcomes type: object + tlsCertificate: + 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 udpPortStatus: properties: annotations: @@ -20912,6 +20961,21 @@ spec: exclude: type: BoolString type: object + tlsCertificate: + properties: + address: + type: string + collectorName: + type: string + exclude: + type: BoolString + expectedCertSubpath: + type: string + httpsProxy: + type: string + required: + - address + type: object udpPortStatus: properties: collectorName: diff --git a/examples/sdk/helm-template/go.mod b/examples/sdk/helm-template/go.mod index 5901e11a5..04e4710fd 100644 --- a/examples/sdk/helm-template/go.mod +++ b/examples/sdk/helm-template/go.mod @@ -11,7 +11,7 @@ replace github.com/replicatedhq/troubleshoot v0.0.0 => ../../../ require ( github.com/replicatedhq/troubleshoot v0.0.0 - helm.sh/helm/v3 v3.17.1 + helm.sh/helm/v3 v3.17.2 sigs.k8s.io/yaml v1.4.0 ) @@ -47,30 +47,30 @@ require ( github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/shopspring/decimal v1.4.0 // indirect - github.com/spf13/cast v1.7.0 // indirect + github.com/spf13/cast v1.7.1 // indirect github.com/x448/float16 v0.8.4 // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect github.com/xeipuuv/gojsonschema v1.2.0 // indirect - golang.org/x/crypto v0.35.0 // indirect - golang.org/x/net v0.36.0 // indirect + golang.org/x/crypto v0.36.0 // indirect + golang.org/x/net v0.38.0 // indirect golang.org/x/oauth2 v0.25.0 // indirect - golang.org/x/sys v0.30.0 // indirect - golang.org/x/term v0.29.0 // indirect - golang.org/x/text v0.22.0 // indirect - golang.org/x/time v0.7.0 // indirect + golang.org/x/sys v0.31.0 // indirect + golang.org/x/term v0.30.0 // indirect + golang.org/x/text v0.23.0 // indirect + golang.org/x/time v0.8.0 // indirect google.golang.org/protobuf v1.36.2 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/api v0.32.2 // indirect - k8s.io/apiextensions-apiserver v0.32.2 // indirect - k8s.io/apimachinery v0.32.2 // indirect - k8s.io/client-go v0.32.2 // indirect + k8s.io/api v0.32.3 // indirect + k8s.io/apiextensions-apiserver v0.32.3 // indirect + k8s.io/apimachinery v0.32.3 // indirect + k8s.io/client-go v0.32.3 // indirect k8s.io/klog/v2 v2.130.1 // indirect k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f // indirect k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect - sigs.k8s.io/controller-runtime v0.20.2 // indirect + sigs.k8s.io/controller-runtime v0.20.4 // indirect sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect sigs.k8s.io/structured-merge-diff/v4 v4.4.2 // indirect ) diff --git a/examples/sdk/helm-template/go.sum b/examples/sdk/helm-template/go.sum index a96ece5d3..3853f96cd 100644 --- a/examples/sdk/helm-template/go.sum +++ b/examples/sdk/helm-template/go.sum @@ -88,8 +88,8 @@ github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= -github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w= -github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y= +github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -110,16 +110,16 @@ github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9dec golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs= -golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ= +golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= +golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.36.0 h1:vWF2fRbw4qslQsQzgFqZff+BItCvGFQqKzKIzx1rmoA= -golang.org/x/net v0.36.0/go.mod h1:bFmbeoIPfrw4sMHNhb4J9f6+tPziuGjq7Jk/38fxi1I= +golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= +golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= golang.org/x/oauth2 v0.25.0 h1:CY4y7XT9v0cRI9oupztF8AgiIu99L/ksR/Xp/6jrZ70= golang.org/x/oauth2 v0.25.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -128,22 +128,22 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= -golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU= -golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s= +golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= +golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y= +golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= -golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= -golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ= -golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= +golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= +golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg= +golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.28.0 h1:WuB6qZ4RPCQo5aP3WdKZS7i595EdWqWR8vqJTlwTVK8= -golang.org/x/tools v0.28.0/go.mod h1:dcIOrVd3mfQKTgrDVQHqCPMWy6lnhfhtX3hLXYVLfRw= +golang.org/x/tools v0.30.0 h1:BgcpHewrV5AUp2G9MebG4XPFI1E2W41zU1SaqVA9vJY= +golang.org/x/tools v0.30.0/go.mod h1:c347cR/OJfw5TI+GfX7RUPNMdDRRbjvYTS0jPyvsVtY= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -161,24 +161,24 @@ 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= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -helm.sh/helm/v3 v3.17.1 h1:gzVoAD+qVuoJU6KDMSAeo0xRJ6N1znRxz3wyuXRmJDk= -helm.sh/helm/v3 v3.17.1/go.mod h1:nvreuhuR+j78NkQcLC3TYoprCKStLyw5P4T7E5itv2w= -k8s.io/api v0.32.2 h1:bZrMLEkgizC24G9eViHGOPbW+aRo9duEISRIJKfdJuw= -k8s.io/api v0.32.2/go.mod h1:hKlhk4x1sJyYnHENsrdCWw31FEmCijNGPJO5WzHiJ6Y= -k8s.io/apiextensions-apiserver v0.32.2 h1:2YMk285jWMk2188V2AERy5yDwBYrjgWYggscghPCvV4= -k8s.io/apiextensions-apiserver v0.32.2/go.mod h1:GPwf8sph7YlJT3H6aKUWtd0E+oyShk/YHWQHf/OOgCA= -k8s.io/apimachinery v0.32.2 h1:yoQBR9ZGkA6Rgmhbp/yuT9/g+4lxtsGYwW6dR6BDPLQ= -k8s.io/apimachinery v0.32.2/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE= -k8s.io/client-go v0.32.2 h1:4dYCD4Nz+9RApM2b/3BtVvBHw54QjMFUl1OLcJG5yOA= -k8s.io/client-go v0.32.2/go.mod h1:fpZ4oJXclZ3r2nDOv+Ux3XcJutfrwjKTCHz2H3sww94= +helm.sh/helm/v3 v3.17.2 h1:agYQ5ew2jq5vdx2K7q5W44KyKQrnSubUMCQsjkiv3/o= +helm.sh/helm/v3 v3.17.2/go.mod h1:+uJKMH/UiMzZQOALR3XUf3BLIoczI2RKKD6bMhPh4G8= +k8s.io/api v0.32.3 h1:Hw7KqxRusq+6QSplE3NYG4MBxZw1BZnq4aP4cJVINls= +k8s.io/api v0.32.3/go.mod h1:2wEDTXADtm/HA7CCMD8D8bK4yuBUptzaRhYcYEEYA3k= +k8s.io/apiextensions-apiserver v0.32.3 h1:4D8vy+9GWerlErCwVIbcQjsWunF9SUGNu7O7hiQTyPY= +k8s.io/apiextensions-apiserver v0.32.3/go.mod h1:8YwcvVRMVzw0r1Stc7XfGAzB/SIVLunqApySV5V7Dss= +k8s.io/apimachinery v0.32.3 h1:JmDuDarhDmA/Li7j3aPrwhpNBA94Nvk5zLeOge9HH1U= +k8s.io/apimachinery v0.32.3/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE= +k8s.io/client-go v0.32.3 h1:RKPVltzopkSgHS7aS98QdscAgtgah/+zmpAogooIqVU= +k8s.io/client-go v0.32.3/go.mod h1:3v0+3k4IcT9bXTc4V2rt+d2ZPPG700Xy6Oi0Gdl2PaY= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f h1:GA7//TjRY9yWGy1poLzYYJJ4JRdzg3+O6e8I+e+8T5Y= k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f/go.mod h1:R/HEjbvWI0qdfb8viZUeVZm0X6IZnxAydC7YU42CMw4= k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 h1:M3sRQVHv7vB20Xc2ybTt7ODCeFj6JSWYFzOFnYeS6Ro= k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -sigs.k8s.io/controller-runtime v0.20.2 h1:/439OZVxoEc02psi1h4QO3bHzTgu49bb347Xp4gW1pc= -sigs.k8s.io/controller-runtime v0.20.2/go.mod h1:xg2XB0K5ShQzAgsoujxuKN4LNXR2LfwwHsPj7Iaw+XY= +sigs.k8s.io/controller-runtime v0.20.4 h1:X3c+Odnxz+iPTRobG4tp092+CvBU9UK0t/bRf+n0DGU= +sigs.k8s.io/controller-runtime v0.20.4/go.mod h1:xg2XB0K5ShQzAgsoujxuKN4LNXR2LfwwHsPj7Iaw+XY= sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 h1:/Rv+M11QRah1itp8VhT6HoVx1Ray9eB4DBr+K+/sCJ8= sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3/go.mod h1:18nIHnGi6636UCz6m8i4DhaJ65T6EruyzmoQqI2BVDo= sigs.k8s.io/structured-merge-diff/v4 v4.4.2 h1:MdmvkGuXi/8io6ixD5wud3vOLwc1rj0aNqRlpuvjmwA= diff --git a/pkg/analyze/host_analyzer.go b/pkg/analyze/host_analyzer.go index fb450ff76..1e2fa098c 100644 --- a/pkg/analyze/host_analyzer.go +++ b/pkg/analyze/host_analyzer.go @@ -67,6 +67,8 @@ func GetHostAnalyzer(analyzer *troubleshootv1beta2.HostAnalyze) (HostAnalyzer, b return &AnalyzeHostNetworkNamespaceConnectivity{analyzer.NetworkNamespaceConnectivity}, true case analyzer.Sysctl != nil: return &AnalyzeHostSysctl{analyzer.Sysctl}, true + case analyzer.TLSCertificate != nil: + return &AnalyzeHostTLSCertificate{analyzer.TLSCertificate}, true default: return nil, false } diff --git a/pkg/analyze/host_tls_certificate.go b/pkg/analyze/host_tls_certificate.go new file mode 100644 index 000000000..cab96a8a3 --- /dev/null +++ b/pkg/analyze/host_tls_certificate.go @@ -0,0 +1,98 @@ +package analyzer + +import ( + "encoding/json" + "fmt" + "strings" + + "github.com/pkg/errors" + "github.com/replicatedhq/troubleshoot/pkg/analyze/types" + troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2" +) + +type AnalyzeHostTLSCertificate struct { + hostAnalyzer *troubleshootv1beta2.TLSAnalyze +} + +func (a *AnalyzeHostTLSCertificate) Title() string { + return hostAnalyzerTitleOrDefault(a.hostAnalyzer.AnalyzeMeta, "TLSCertificate") +} + +func (a *AnalyzeHostTLSCertificate) IsExcluded() (bool, error) { + return isExcluded(a.hostAnalyzer.Exclude) +} + +func (a *AnalyzeHostTLSCertificate) Analyze( + getCollectedFileContents func(string) ([]byte, error), findFiles getChildCollectedFileContents, +) ([]*AnalyzeResult, error) { + + collectorName := a.hostAnalyzer.CollectorName + if collectorName == "" { + return nil, fmt.Errorf("collector name is required") + } + + const nodeBaseDir = "host-collectors/tls-certificate" + localPath := fmt.Sprintf("%s/%s.json", nodeBaseDir, collectorName) + fileName := fmt.Sprintf("%s.json", collectorName) + + collectedContents, err := retrieveCollectedContents( + getCollectedFileContents, + localPath, + nodeBaseDir, + fileName, + ) + if err != nil { + return []*AnalyzeResult{{Title: a.Title()}}, err + } + + results, err := analyzeHostCollectorResults(collectedContents, a.hostAnalyzer.Outcomes, a.CheckCondition, a.Title()) + if err != nil { + return nil, errors.Wrap(err, "failed to analyze http request") + } + + return results, nil +} + +func (a *AnalyzeHostTLSCertificate) CheckCondition(when string, data []byte) (bool, error) { + var tlsInfo types.TLSInfo + if err := json.Unmarshal(data, &tlsInfo); err != nil { + return false, fmt.Errorf("failed to unmarshal data into tlsInfo: %v", err) + } + return compareHostTLSResult(when, &tlsInfo) +} + +// currently this supports only checks like "issuer == foo" +func compareHostTLSResult(when string, tlsInfo *types.TLSInfo) (bool, error) { + parts := strings.Split(when, " ") + + checkType := parts[0] + switch checkType { + case "issuer": + // check that the issuer matches the provided expected value + if len(parts) < 3 { + return false, fmt.Errorf("invalid when clause: %s", when) + } + + expected := strings.Join(parts[2:], " ") + + for _, cert := range tlsInfo.PeerCertificates { + if cert.Issuer == expected { + return true, nil + } + } + return false, nil + case "matchesExpected": + // check that the certificate's information matches what the server returned inside the response body + if tlsInfo.ExpectedCerts == nil { + return false, fmt.Errorf("expected certs not found in response") + } + + // only check the issuer of the first expected cert today + expected := tlsInfo.ExpectedCerts[0] + comparison := tlsInfo.PeerCertificates[0] + + return expected.Issuer == comparison.Issuer, nil + default: + return false, fmt.Errorf("invalid check type: %s", checkType) + } +} diff --git a/pkg/analyze/host_tls_certificate_test.go b/pkg/analyze/host_tls_certificate_test.go new file mode 100644 index 000000000..072811398 --- /dev/null +++ b/pkg/analyze/host_tls_certificate_test.go @@ -0,0 +1,349 @@ +package analyzer + +import ( + "encoding/json" + "testing" + + "github.com/replicatedhq/troubleshoot/pkg/analyze/types" + troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestAnalyzeHostTLS(t *testing.T) { + tests := []struct { + name string + tlsInfo *types.TLSInfo + hostAnalyzer *troubleshootv1beta2.TLSAnalyze + result []*AnalyzeResult + expectErr bool + }{ + { + name: "issuer passes", + tlsInfo: &types.TLSInfo{ + PeerCertificates: []types.CertInfo{ + { + Issuer: "foo", + }, + }, + }, + hostAnalyzer: &troubleshootv1beta2.TLSAnalyze{ + CollectorName: "test-tls", + Outcomes: []*troubleshootv1beta2.Outcome{ + { + Fail: &troubleshootv1beta2.SingleOutcome{ + When: "issuer == abc", + Message: "issuer was abc", + }, + }, + { + Pass: &troubleshootv1beta2.SingleOutcome{ + When: "issuer == foo", + Message: "issuer was foo", + }, + }, + { + Warn: &troubleshootv1beta2.SingleOutcome{ + When: "issuer == bar", + Message: "issuer was bar", + }, + }, + }, + }, + result: []*AnalyzeResult{ + { + Title: "TLSCertificate", + IsPass: true, + Message: "issuer was foo", + }, + }, + }, + + { + name: "invalid check type", + tlsInfo: &types.TLSInfo{ + PeerCertificates: []types.CertInfo{ + { + Issuer: "foo", + }, + }, + }, + hostAnalyzer: &troubleshootv1beta2.TLSAnalyze{ + CollectorName: "test-tls", + Outcomes: []*troubleshootv1beta2.Outcome{ + { + Fail: &troubleshootv1beta2.SingleOutcome{ + When: "this is invalid", + }, + }, + }, + }, + expectErr: true, + }, + { + name: "fallthrough to fail", + tlsInfo: &types.TLSInfo{ + PeerCertificates: []types.CertInfo{ + { + Issuer: "foo", + }, + }, + }, + hostAnalyzer: &troubleshootv1beta2.TLSAnalyze{ + CollectorName: "test-tls", + Outcomes: []*troubleshootv1beta2.Outcome{ + { + Pass: &troubleshootv1beta2.SingleOutcome{ + When: "issuer == abc", + Message: "issuer was abc", + }, + }, + { + Fail: &troubleshootv1beta2.SingleOutcome{ + When: "", + Message: "issuer was not abc", + }, + }, + }, + }, + result: []*AnalyzeResult{ + { + Title: "TLSCertificate", + IsFail: true, + Message: "issuer was not abc", + }, + }, + }, + { + name: "second cert matches", + tlsInfo: &types.TLSInfo{ + PeerCertificates: []types.CertInfo{ + { + Issuer: "foo", + }, + { + Issuer: "bar", + }, + }, + }, + hostAnalyzer: &troubleshootv1beta2.TLSAnalyze{ + CollectorName: "test-tls", + Outcomes: []*troubleshootv1beta2.Outcome{ + { + Pass: &troubleshootv1beta2.SingleOutcome{ + When: "issuer == bar", + Message: "issuer was bar", + }, + }, + { + Fail: &troubleshootv1beta2.SingleOutcome{ + When: "", + Message: "issuer was not bar", + }, + }, + }, + }, + result: []*AnalyzeResult{ + { + Title: "TLSCertificate", + IsPass: true, + Message: "issuer was bar", + }, + }, + }, + { + name: "replicated.app results", + // {"peer_certificates":[{"issuer":"E5","subject":"replicated.app","serial":"366647399446765119739694467366731491294821","not_before":"2025-02-12T20:48:06Z","not_after":"2025-05-13T20:48:05Z","is_ca":false,"raw":"MIIDkDCCAxagAwIBAgISBDV62JAIFJ/68CYg4GL6V75lMAoGCCqGSM49BAMDMDIxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MQswCQYDVQQDEwJFNTAeFw0yNTAyMTIyMDQ4MDZaFw0yNTA1MTMyMDQ4MDVaMBkxFzAVBgNVBAMTDnJlcGxpY2F0ZWQuYXBwMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEtLBSWSjTkS63NvKjChgv0IRLXQ+8qQVZfGa27M8Odvok+0nDivOLwvXToIfcsb87rj2ZolYaMZ51oZMAPTer8KOCAiMwggIfMA4GA1UdDwEB/wQEAwIHgDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUAcqA0Ax0SYkbpaTHUE1/3K+F+8MwHwYDVR0jBBgwFoAUnytfzzwhT50Et+0rLMTGcIvS1w0wVQYIKwYBBQUHAQEESTBHMCEGCCsGAQUFBzABhhVodHRwOi8vZTUuby5sZW5jci5vcmcwIgYIKwYBBQUHMAKGFmh0dHA6Ly9lNS5pLmxlbmNyLm9yZy8wKwYDVR0RBCQwIoIQKi5yZXBsaWNhdGVkLmFwcIIOcmVwbGljYXRlZC5hcHAwEwYDVR0gBAwwCjAIBgZngQwBAgEwggEFBgorBgEEAdZ5AgQCBIH2BIHzAPEAdgDM+w9qhXEJZf6Vm1PO6bJ8IumFXA2XjbapflTA/kwNsAAAAZT8INAKAAAEAwBHMEUCIQChEKizx3gdCm8rATD9JULAHFmhCcNCiPjLaKIuyV624AIgDIxiox82edgyw1ENlsCTVgM849qDBOLr41WeSIG04kwAdwATSt8atZhCCXgMb+9MepGkFrcjSc5YV2rfrtqnwqvgIgAAAZT8INHBAAAEAwBIMEYCIQCuxLNpzW2lYKwJ2uLBSu14wH0jc+oxrH4lA/QLXm2CeQIhAM66PhzUGhgrypwyKW0F9AwH73tPHoRZHWgDnWBjX7B+MAoGCCqGSM49BAMDA2gAMGUCMFMMHRfHegdMljQd68qhTv6hL0ySV9nn2u85mWcilDHUMbQSGaqH8liyMfNlR/a7+gIxALSdKsdnyrqAsViMDAMo56gwaq8EeGtSyfWfmPiRlRinn6ANwxf6bs6J3csBgM+hdw=="},{"issuer":"ISRG Root X1","subject":"E5","serial":"174873564306387906651619802726858882526","not_before":"2024-03-13T00:00:00Z","not_after":"2027-03-12T23:59:59Z","is_ca":true,"raw":"MIIEVzCCAj+gAwIBAgIRAIOPbGPOsTmMYgZigxXJ/d4wDQYJKoZIhvcNAQELBQAwTzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2VhcmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMjQwMzEzMDAwMDAwWhcNMjcwMzEyMjM1OTU5WjAyMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNTGV0J3MgRW5jcnlwdDELMAkGA1UEAxMCRTUwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQNCzqKa2GOtu/cX1jnxkJFVKtj9mZhSAouWXW0gQI3ULc/FnncmOyhKJdyIBwsz9V8UiBOVHhbhBRrwJCuhezAUUE8Wod/Bk3U/mDR+mwt4X2VEIiiCFQPmRpM5uoKrNijgfgwgfUwDgYDVR0PAQH/BAQDAgGGMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEFBQcDATASBgNVHRMBAf8ECDAGAQH/AgEAMB0GA1UdDgQWBBSfK1/PPCFPnQS37SssxMZwi9LXDTAfBgNVHSMEGDAWgBR5tFnme7bl5AFzgAiIyBpY9umbbjAyBggrBgEFBQcBAQQmMCQwIgYIKwYBBQUHMAKGFmh0dHA6Ly94MS5pLmxlbmNyLm9yZy8wEwYDVR0gBAwwCjAIBgZngQwBAgEwJwYDVR0fBCAwHjAcoBqgGIYWaHR0cDovL3gxLmMubGVuY3Iub3JnLzANBgkqhkiG9w0BAQsFAAOCAgEAH3KdNEVCQdqk0LKyuNImTKdRJY1C2uw2SJajuhqkyGPY8C+zzsufZ+mgnhnq1A2KVQOSykOEnUbx1cy637rBAihx97r+bcwbZM6sTDIaEriR/PLk6LKs9Be0uoVxgOKDcpG9svD33J+G9Lcfv1K9luDmSTgG6XNFIN5vfI5gs/lMPyojEMdIzK9blcl2/1vKxO8WGCcjvsQ1nJ/Pwt8LQZBfOFyVXP8ubAp/au3dc4EKWG9MO5zcx1qT9+NXRGdVWxGvmBFRAajciMfXME1ZuGmk3/GOkoAM7ZkjZmleyokP1LGzmfJcUd9s7eeu1/9/eg5XlXd/55GtYjAM+C4DG5i7eaNqcm2F+yxYIPt6cbbtYVNJCGfHWqHEQ4FYStUyFnv8sjyqU8ypgZaNJ9aVcWSICLOIE1/Qv/7oKsnZCWJ926wU6RqG1OYPGOi1zuABhLw61cuPVDT28nQS/e6z95cJXq0eK1BcaJ6fJZsmbjRgD5p3mvEf5vdQM7MCEvU0tHbsx2I5mHHJoABHb8KVBgWp/lcXGWiWaeOyB7RP+OfDtvi2OsapxXiV7vNVs7fMlrRjY1joKaqmmycnBvAq14AEbtyLsVfOS66B8apkeFX2NY4XPEYV4ZSCe8VHPrdrERk2wILG3T/EGmSIkCYVUMSnjmJdVQD9F6Na/+zmXCc="}]} + tlsInfo: &types.TLSInfo{ + PeerCertificates: []types.CertInfo{ + { + Issuer: "E5", + Subject: "replicated.app", + Serial: "366647399446765119739694467366731491294821", + NotBefore: "2025-02-12T20:48:06Z", + NotAfter: "2025-05-13T20:48:05Z", + IsCA: false, + }, + { + Issuer: "ISRG Root X1", + Subject: "E5", + Serial: "174873564306387906651619802726858882526", + NotBefore: "2024-03-13T00:00:00Z", + NotAfter: "2027-03-12T23:59:59Z", + IsCA: true, + }, + }, + }, + hostAnalyzer: &troubleshootv1beta2.TLSAnalyze{ + CollectorName: "test-tls", + Outcomes: []*troubleshootv1beta2.Outcome{ + { + Pass: &troubleshootv1beta2.SingleOutcome{ + When: "issuer == E5", + Message: "The issuer for replicated.app is E5 (letsencrypt) as expected", + }, + }, + { + Fail: &troubleshootv1beta2.SingleOutcome{ + When: "", + Message: "The issuer for replicated.app is not E5 (letsencrypt)", + }, + }, + }, + }, + result: []*AnalyzeResult{ + { + Title: "TLSCertificate", + IsPass: true, + Message: "The issuer for replicated.app is E5 (letsencrypt) as expected", + }, + }, + }, + + { + name: "replicated.app comparison results", + tlsInfo: &types.TLSInfo{ + PeerCertificates: []types.CertInfo{ + { + Issuer: "E5", + Subject: "replicated.app", + Serial: "366647399446765119739694467366731491294821", + NotBefore: "2025-02-12T20:48:06Z", + NotAfter: "2025-05-13T20:48:05Z", + IsCA: false, + }, + { + Issuer: "ISRG Root X1", + Subject: "E5", + Serial: "174873564306387906651619802726858882526", + NotBefore: "2024-03-13T00:00:00Z", + NotAfter: "2027-03-12T23:59:59Z", + IsCA: true, + }, + }, + ExpectedCerts: []types.CertInfo{ + { + Issuer: "E5", + Subject: "replicated.app", + Serial: "366647399446765119739694467366731491294821", + NotBefore: "2025-02-12T20:48:06Z", + NotAfter: "2025-05-13T20:48:05Z", + IsCA: false, + }, + { + Issuer: "ISRG Root X1", + Subject: "E5", + Serial: "174873564306387906651619802726858882526", + NotBefore: "2024-03-13T00:00:00Z", + NotAfter: "2027-03-12T23:59:59Z", + IsCA: true, + }, + }, + }, + hostAnalyzer: &troubleshootv1beta2.TLSAnalyze{ + CollectorName: "test-tls", + Outcomes: []*troubleshootv1beta2.Outcome{ + { + Pass: &troubleshootv1beta2.SingleOutcome{ + When: "matchesExpected", + Message: "The issuer for replicated.app is as expected", + }, + }, + { + Fail: &troubleshootv1beta2.SingleOutcome{ + When: "", + Message: "The issuer for replicated.app is not as expected", + }, + }, + }, + }, + result: []*AnalyzeResult{ + { + Title: "TLSCertificate", + IsPass: true, + Message: "The issuer for replicated.app is as expected", + }, + }, + }, + + { + name: "replicated.app MITM comparison results", + tlsInfo: &types.TLSInfo{ + PeerCertificates: []types.CertInfo{ + { + Issuer: "foo", + Subject: "replicated.app", + Serial: "different", + NotBefore: "2025-02-12T20:48:06Z", + NotAfter: "2025-05-13T20:48:05Z", + IsCA: false, + }, + }, + ExpectedCerts: []types.CertInfo{ + { + Issuer: "E5", + Subject: "replicated.app", + Serial: "366647399446765119739694467366731491294821", + NotBefore: "2025-02-12T20:48:06Z", + NotAfter: "2025-05-13T20:48:05Z", + IsCA: false, + }, + { + Issuer: "ISRG Root X1", + Subject: "E5", + Serial: "174873564306387906651619802726858882526", + NotBefore: "2024-03-13T00:00:00Z", + NotAfter: "2027-03-12T23:59:59Z", + IsCA: true, + }, + }, + }, + hostAnalyzer: &troubleshootv1beta2.TLSAnalyze{ + CollectorName: "test-tls", + Outcomes: []*troubleshootv1beta2.Outcome{ + { + Pass: &troubleshootv1beta2.SingleOutcome{ + When: "matchesExpected", + Message: "The issuer for replicated.app is as expected", + }, + }, + { + Fail: &troubleshootv1beta2.SingleOutcome{ + When: "", + Message: "The issuer for replicated.app is not as expected", + }, + }, + }, + }, + result: []*AnalyzeResult{ + { + Title: "TLSCertificate", + IsFail: true, + Message: "The issuer for replicated.app is not as expected", + }, + }, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + req := require.New(t) + b, err := json.Marshal(test.tlsInfo) + if err != nil { + t.Fatal(err) + } + + getCollectedFileContents := func(filename string) ([]byte, error) { + return b, nil + } + + result, err := (&AnalyzeHostTLSCertificate{test.hostAnalyzer}).Analyze(getCollectedFileContents, nil) + if test.expectErr { + req.Error(err) + } else { + req.NoError(err) + } + + assert.Equal(t, test.result, result) + }) + } +} diff --git a/pkg/analyze/types/restic_types.go b/pkg/analyze/types/restic_types.go index 2161e04e4..42beae61a 100644 --- a/pkg/analyze/types/restic_types.go +++ b/pkg/analyze/types/restic_types.go @@ -1,4 +1,4 @@ -package analyzer +package types import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" diff --git a/pkg/analyze/types/tls_types.go b/pkg/analyze/types/tls_types.go new file mode 100644 index 000000000..ec3b6b79f --- /dev/null +++ b/pkg/analyze/types/tls_types.go @@ -0,0 +1,17 @@ +package types + +type CertInfo struct { + Issuer string `json:"issuer"` + Subject string `json:"subject"` + Serial string `json:"serial"` + NotBefore string `json:"not_before"` + NotAfter string `json:"not_after"` + IsCA bool `json:"is_ca"` + Raw []byte `json:"raw"` +} + +type TLSInfo struct { + PeerCertificates []CertInfo `json:"peer_certificates"` + ExpectedCerts []CertInfo `json:"expected_certs"` + Error string `json:"error"` +} diff --git a/pkg/apis/troubleshoot/v1beta2/hostanalyzer_shared.go b/pkg/apis/troubleshoot/v1beta2/hostanalyzer_shared.go index 1b1e8cd51..332642191 100644 --- a/pkg/apis/troubleshoot/v1beta2/hostanalyzer_shared.go +++ b/pkg/apis/troubleshoot/v1beta2/hostanalyzer_shared.go @@ -60,6 +60,12 @@ type TimeAnalyze struct { Outcomes []*Outcome `json:"outcomes" yaml:"outcomes"` } +type TLSAnalyze struct { + AnalyzeMeta `json:",inline" yaml:",inline"` + CollectorName string `json:"collectorName,omitempty" yaml:"collectorName,omitempty"` + Outcomes []*Outcome `json:"outcomes" yaml:"outcomes"` +} + type BlockDevicesAnalyze struct { AnalyzeMeta `json:",inline" yaml:",inline"` CollectorName string `json:"collectorName,omitempty" yaml:"collectorName,omitempty"` @@ -176,4 +182,5 @@ type HostAnalyze struct { JsonCompare *JsonCompare `json:"jsonCompare,omitempty" yaml:"jsonCompare,omitempty"` NetworkNamespaceConnectivity *NetworkNamespaceConnectivityAnalyze `json:"networkNamespaceConnectivity,omitempty" yaml:"networkNamespaceConnectivity,omitempty"` Sysctl *HostSysctlAnalyze `json:"sysctl,omitempty" yaml:"sysctl,omitempty"` + TLSCertificate *TLSAnalyze `json:"tlsCertificate,omitempty" yaml:"tlsCertificate,omitempty"` } diff --git a/pkg/apis/troubleshoot/v1beta2/hostcollector_shared.go b/pkg/apis/troubleshoot/v1beta2/hostcollector_shared.go index 383d6102a..26cbf842d 100644 --- a/pkg/apis/troubleshoot/v1beta2/hostcollector_shared.go +++ b/pkg/apis/troubleshoot/v1beta2/hostcollector_shared.go @@ -235,6 +235,13 @@ type HostSysctl struct { HostCollectorMeta `json:",inline" yaml:",inline"` } +type HostTLSCertificate struct { + HostCollectorMeta `json:",inline" yaml:",inline"` + Address string `json:"address"` + HttpsProxy string `json:"httpsProxy,omitempty"` + ExpectedCertSubpath string `json:"expectedCertSubpath,omitempty"` +} + type HostCollect struct { CPU *CPU `json:"cpu,omitempty" yaml:"cpu,omitempty"` Memory *Memory `json:"memory,omitempty" yaml:"memory,omitempty"` @@ -265,6 +272,7 @@ type HostCollect struct { HostDNS *HostDNS `json:"dns,omitempty" yaml:"dns,omitempty"` NetworkNamespaceConnectivity *HostNetworkNamespaceConnectivity `json:"networkNamespaceConnectivity,omitempty" yaml:"networkNamespaceConnectivity,omitempty"` HostSysctl *HostSysctl `json:"sysctl,omitempty" yaml:"sysctl,omitempty"` + HostTLSCertificate *HostTLSCertificate `json:"tlsCertificate,omitempty" yaml:"tlsCertificate,omitempty"` } // GetName gets the name of the collector diff --git a/pkg/apis/troubleshoot/v1beta2/zz_generated.deepcopy.go b/pkg/apis/troubleshoot/v1beta2/zz_generated.deepcopy.go index ffe34243d..f2b385809 100644 --- a/pkg/apis/troubleshoot/v1beta2/zz_generated.deepcopy.go +++ b/pkg/apis/troubleshoot/v1beta2/zz_generated.deepcopy.go @@ -1973,6 +1973,11 @@ func (in *HostAnalyze) DeepCopyInto(out *HostAnalyze) { *out = new(HostSysctlAnalyze) (*in).DeepCopyInto(*out) } + if in.TLSCertificate != nil { + in, out := &in.TLSCertificate, &out.TLSCertificate + *out = new(TLSAnalyze) + (*in).DeepCopyInto(*out) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HostAnalyze. @@ -2213,6 +2218,11 @@ func (in *HostCollect) DeepCopyInto(out *HostCollect) { *out = new(HostSysctl) (*in).DeepCopyInto(*out) } + if in.HostTLSCertificate != nil { + in, out := &in.HostTLSCertificate, &out.HostTLSCertificate + *out = new(HostTLSCertificate) + (*in).DeepCopyInto(*out) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HostCollect. @@ -2903,6 +2913,22 @@ func (in *HostSystemPackages) DeepCopy() *HostSystemPackages { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *HostTLSCertificate) DeepCopyInto(out *HostTLSCertificate) { + *out = *in + in.HostCollectorMeta.DeepCopyInto(&out.HostCollectorMeta) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HostTLSCertificate. +func (in *HostTLSCertificate) DeepCopy() *HostTLSCertificate { + if in == nil { + return nil + } + out := new(HostTLSCertificate) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *HostTime) DeepCopyInto(out *HostTime) { *out = *in @@ -5225,6 +5251,33 @@ func (in *TCPPortStatusAnalyze) DeepCopy() *TCPPortStatusAnalyze { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TLSAnalyze) DeepCopyInto(out *TLSAnalyze) { + *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 TLSAnalyze. +func (in *TLSAnalyze) DeepCopy() *TLSAnalyze { + if in == nil { + return nil + } + out := new(TLSAnalyze) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *TLSParams) DeepCopyInto(out *TLSParams) { *out = *in diff --git a/pkg/collect/host_collector.go b/pkg/collect/host_collector.go index 855c06ecd..e72ec29d9 100644 --- a/pkg/collect/host_collector.go +++ b/pkg/collect/host_collector.go @@ -105,6 +105,8 @@ func GetHostCollector(collector *troubleshootv1beta2.HostCollect, bundlePath str return &CollectHostNetworkNamespaceConnectivity{collector.NetworkNamespaceConnectivity, bundlePath}, true case collector.HostSysctl != nil: return &CollectHostSysctl{collector.HostSysctl, bundlePath}, true + case collector.HostTLSCertificate != nil: + return &CollectHostTLSCertificate{collector.HostTLSCertificate, bundlePath}, true default: return nil, false } diff --git a/pkg/collect/host_tls_certificate.go b/pkg/collect/host_tls_certificate.go new file mode 100644 index 000000000..2514a87d5 --- /dev/null +++ b/pkg/collect/host_tls_certificate.go @@ -0,0 +1,97 @@ +package collect + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "path/filepath" + "strings" + "time" + + "github.com/pkg/errors" + "github.com/replicatedhq/troubleshoot/pkg/analyze/types" + troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2" +) + +type CollectHostTLSCertificate struct { + hostCollector *troubleshootv1beta2.HostTLSCertificate + BundlePath string +} + +func (c *CollectHostTLSCertificate) Title() string { + return hostCollectorTitleOrDefault(c.hostCollector.HostCollectorMeta, "TCP Port Status") +} + +func (c *CollectHostTLSCertificate) IsExcluded() (bool, error) { + return isExcluded(c.hostCollector.Exclude) +} + +func (c *CollectHostTLSCertificate) Collect(progressChan chan<- interface{}) (map[string][]byte, error) { + tlsInfo := types.TLSInfo{} + + headers := map[string]string{ + "tls-request-hostname": c.hostCollector.Address, + } + + resp, err := doRequest("GET", fmt.Sprintf("https://%s/%s", c.hostCollector.Address, c.hostCollector.ExpectedCertSubpath), headers, "", true, "", nil, c.hostCollector.HttpsProxy) + if err != nil { + tlsInfo.Error = err.Error() + } else { + defer resp.Body.Close() + certs := resp.TLS.PeerCertificates + cleanedCerts := make([]types.CertInfo, len(certs)) + for i, cert := range certs { + cleanedCerts[i] = types.CertInfo{ + Issuer: cert.Issuer.CommonName, + Subject: cert.Subject.CommonName, + Serial: cert.SerialNumber.String(), + NotBefore: cert.NotBefore.Format(time.RFC3339), + NotAfter: cert.NotAfter.Format(time.RFC3339), + IsCA: cert.IsCA, + Raw: cert.Raw, + } + } + + tlsInfo.PeerCertificates = cleanedCerts + + if c.hostCollector.ExpectedCertSubpath != "" { + readBody, err := io.ReadAll(resp.Body) + if err != nil { + tlsInfo.Error = fmt.Sprintf("failed to read response body: %s", err) + } + + // parse the response body as a JSON object + var expectedCerts []types.CertInfo + if err := json.Unmarshal(readBody, &expectedCerts); err != nil { + tlsInfo.Error = fmt.Sprintf("failed to unmarshal response body as JSON: %s", err) + } + tlsInfo.ExpectedCerts = expectedCerts + } + } + + b, err := json.Marshal(tlsInfo) + if err != nil { + return nil, errors.Wrap(err, "failed to marshal tls info") + } + + collectorName := c.hostCollector.CollectorName + if collectorName == "" { + collectorName = strings.ReplaceAll(c.hostCollector.Address, ":", "-") + } + name := filepath.Join("host-collectors/tls-certificate", collectorName+".json") + + output := NewResult() + err = output.SaveResult(c.BundlePath, name, bytes.NewBuffer(b)) + if err != nil { + return nil, errors.Wrap(err, "failed to save result") + } + + return map[string][]byte{ + name: b, + }, nil +} + +func (c *CollectHostTLSCertificate) RemoteCollect(progressChan chan<- interface{}) (map[string][]byte, error) { + return nil, ErrRemoteCollectorNotImplemented +} diff --git a/pkg/collect/host_tls_certificate_test.go b/pkg/collect/host_tls_certificate_test.go new file mode 100644 index 000000000..86048bfa3 --- /dev/null +++ b/pkg/collect/host_tls_certificate_test.go @@ -0,0 +1,240 @@ +package collect + +import ( + "crypto/rand" + "crypto/rsa" + "crypto/tls" + "crypto/x509" + "crypto/x509/pkix" + "encoding/json" + "encoding/pem" + "fmt" + "math/big" + "net/http" + "net/http/httptest" + "os" + "path/filepath" + "strings" + "testing" + "time" + + "github.com/pkg/errors" + "github.com/replicatedhq/troubleshoot/pkg/analyze/types" + troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestCollectHostTLS_Collect(t *testing.T) { + // Create a self-signed certificate for testing + cert, key, err := generateTestSelfSignedCert() + require.NoError(t, err) + + // Start a test TLSCertificate server + serverAddr, closeServer, err := startTestHttpsServer(cert, key) + require.NoError(t, err) + defer closeServer() + + // Create a temporary directory for the bundle + bundlePath, err := os.MkdirTemp("", "tls-test") + require.NoError(t, err) + defer os.RemoveAll(bundlePath) + + // Create the necessary subdirectories + hostTimePath := filepath.Join(bundlePath, "host-collectors", "time") + err = os.MkdirAll(hostTimePath, 0755) + require.NoError(t, err) + + type certFields struct { + Issuer string `json:"issuer"` + Subject string `json:"subject"` + IsCA bool `json:"is_ca"` + } + + tests := []struct { + name string + hostCollector *troubleshootv1beta2.HostTLSCertificate + certFields []certFields + expectedCerts []certFields + wantErr bool + }{ + { + name: "successful collection", + hostCollector: &troubleshootv1beta2.HostTLSCertificate{ + Address: serverAddr, + HostCollectorMeta: troubleshootv1beta2.HostCollectorMeta{ + CollectorName: "test-tls", + }, + }, + certFields: []certFields{ + { + Issuer: "localhost", + Subject: "localhost", + IsCA: false, + }, + }, + wantErr: false, + }, + { + name: "failed connection", + hostCollector: &troubleshootv1beta2.HostTLSCertificate{ + Address: "invalid-address:9999", + HostCollectorMeta: troubleshootv1beta2.HostCollectorMeta{ + CollectorName: "test-tls-failed", + }, + }, + wantErr: true, + }, + { + name: "successful collection with expected cert", + hostCollector: &troubleshootv1beta2.HostTLSCertificate{ + Address: serverAddr, + ExpectedCertSubpath: "expected-cert", + HostCollectorMeta: troubleshootv1beta2.HostCollectorMeta{ + CollectorName: "test-tls", + }, + }, + certFields: []certFields{ + { + Issuer: "localhost", + Subject: "localhost", + IsCA: false, + }, + }, + expectedCerts: []certFields{ + { + Issuer: "expected", + Subject: serverAddr, + IsCA: true, + }, + }, + wantErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &CollectHostTLSCertificate{ + hostCollector: tt.hostCollector, + BundlePath: bundlePath, + } + + collected, err := c.Collect(nil) + require.NoError(t, err) + require.NotNil(t, collected) + + expectedFilename := filepath.Join("host-collectors/tls-certificate", tt.hostCollector.CollectorName+".json") + assert.Contains(t, collected, expectedFilename) + + // Validate the content + var tlsInfo types.TLSInfo + err = json.Unmarshal(collected[expectedFilename], &tlsInfo) + + require.NoError(t, err) + + if tt.wantErr { + require.NotNil(t, tlsInfo.Error) + return + } + require.Equal(t, "", tlsInfo.Error) + + // Verify we have certificate information + require.NotEmpty(t, tlsInfo.PeerCertificates) + + // Verify the certificate fields match the expected values + require.Equal(t, len(tt.certFields), len(tlsInfo.PeerCertificates)) + for i, crt := range tlsInfo.PeerCertificates { + assert.Equal(t, tt.certFields[i].Issuer, crt.Issuer) + assert.Equal(t, tt.certFields[i].Subject, crt.Subject) + assert.Equal(t, tt.certFields[i].IsCA, crt.IsCA) + } + + // verify that the expected certs array (returned by the server) matches the expected certs array + require.Equal(t, len(tt.expectedCerts), len(tlsInfo.ExpectedCerts)) + for i, crt := range tlsInfo.ExpectedCerts { + assert.Equal(t, tt.expectedCerts[i].Issuer, crt.Issuer) + assert.Equal(t, tt.expectedCerts[i].Subject, crt.Subject) + assert.Equal(t, tt.expectedCerts[i].IsCA, crt.IsCA) + } + }) + } +} + +// Helper function to generate a self-signed certificate for testing +func generateTestSelfSignedCert() ([]byte, []byte, error) { + // Generate a new private key + privateKey, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + return nil, nil, errors.Wrap(err, "failed to generate private key") + } + + // Create certificate template + serialNumber, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128)) + if err != nil { + return nil, nil, errors.Wrap(err, "failed to generate serial number") + } + + template := x509.Certificate{ + SerialNumber: serialNumber, + Subject: pkix.Name{ + CommonName: "localhost", + }, + Issuer: pkix.Name{ + CommonName: "localhost", + }, + NotBefore: time.Now(), + NotAfter: time.Now().Add(time.Hour), + KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + BasicConstraintsValid: true, + DNSNames: []string{"localhost"}, + } + + // Create certificate using the template + derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &privateKey.PublicKey, privateKey) + if err != nil { + return nil, nil, errors.Wrap(err, "failed to create certificate") + } + + // Convert private key to DER format + privateKeyDER := x509.MarshalPKCS1PrivateKey(privateKey) + + return derBytes, privateKeyDER, nil +} + +// Helper function to start a test TLSCertificate server +func startTestHttpsServer(certDER, keyDER []byte) (string, func(), error) { + // Encode certificate and key in PEM format + certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER}) + keyPEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: keyDER}) + + pair, err := tls.X509KeyPair(certPEM, keyPEM) + if err != nil { + return "", nil, errors.Wrap(err, "failed to create X509 key pair") + } + + // Create a simple HTTP handler + handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + fmt.Println(r.URL.Path) + if r.URL.Path == "/expected-cert" { + w.WriteHeader(http.StatusOK) + subject := r.Header.Get("tls-request-hostname") + resp := fmt.Sprintf(`[{"issuer": "expected", "subject": "%s", "serial": "1234567890", "not_before": "abc", "not_after": "xyz", "is_ca": true}]`, subject) + w.Write([]byte(resp)) + return + } + w.WriteHeader(http.StatusOK) + w.Write([]byte("TLSCertificate Test Server")) + }) + + // Use httptest package to create a TLSCertificate server + testServer := httptest.NewUnstartedServer(handler) + testServer.TLS = &tls.Config{ + Certificates: []tls.Certificate{pair}, + } + testServer.StartTLS() + + addr := strings.TrimPrefix(testServer.URL, "https://") + + return addr, testServer.Close, nil +} diff --git a/pkg/collect/http_test.go b/pkg/collect/http_test.go index 46fd216e5..b401254e6 100644 --- a/pkg/collect/http_test.go +++ b/pkg/collect/http_test.go @@ -264,7 +264,7 @@ func TestCollectHTTP_Collect(t *testing.T) { wantErr: false, }, { - name: "TLS: certificate is not trusted", + name: "TLSCertificate: certificate is not trusted", Collector: &troubleshootv1beta2.HTTP{ CollectorMeta: troubleshootv1beta2.CollectorMeta{ CollectorName: "", diff --git a/pkg/collect/postgres.go b/pkg/collect/postgres.go index b2a82609e..f1f837e89 100644 --- a/pkg/collect/postgres.go +++ b/pkg/collect/postgres.go @@ -41,22 +41,22 @@ func (c *CollectPostgres) createConnectConfig() (*pgx.ConnConfig, error) { } if c.Collector.TLS != nil { - klog.V(2).Infof("Connecting to postgres with TLS client config") - // Set the libpq TLS environment variables since pgx parses them to - // create the TLS configuration (tls.Config instance) to connect with + klog.V(2).Infof("Connecting to postgres with TLSCertificate client config") + // Set the libpq TLSCertificate environment variables since pgx parses them to + // create the TLSCertificate configuration (tls.Config instance) to connect with // https://www.postgresql.org/docs/current/libpq-envars.html caCert, clientCert, clientKey, err := getTLSParamTriplet(c.Context, c.Client, c.Collector.TLS) if err != nil { return nil, err } - // Drop the TLS params to files and set the paths to their + // Drop the TLSCertificate params to files and set the paths to their // respective environment variables // The environment variables are unset after the connection config // is created. Their respective files are deleted as well. tmpdir, err := os.MkdirTemp("", "ts-postgres-collector") if err != nil { - return nil, errors.Wrap(err, "failed to create temp dir to store postgres collector TLS files") + return nil, errors.Wrap(err, "failed to create temp dir to store postgres collector TLSCertificate files") } defer os.RemoveAll(tmpdir) diff --git a/pkg/collect/redis.go b/pkg/collect/redis.go index 2a0d11a73..da72677bc 100644 --- a/pkg/collect/redis.go +++ b/pkg/collect/redis.go @@ -40,7 +40,7 @@ func (c *CollectRedis) createClient() (*redis.Client, error) { } if c.Collector.TLS != nil { - klog.V(2).Infof("Connecting to redis in mutual TLS") + klog.V(2).Infof("Connecting to redis in mutual TLSCertificate") return c.createMTLSClient(opt) } diff --git a/pkg/collect/util_test.go b/pkg/collect/util_test.go index 5e2e40fec..9c28be400 100644 --- a/pkg/collect/util_test.go +++ b/pkg/collect/util_test.go @@ -168,7 +168,7 @@ func Test_createTLSConfig(t *testing.T) { caCertOnly: true, }, { - name: "empty TLS parameters fails to create config with error", + name: "empty TLSCertificate parameters fails to create config with error", hasError: true, }, { @@ -239,7 +239,7 @@ func Test_createTLSConfig(t *testing.T) { assert.Nil(t, tlsCfg.RootCAs) assert.Nil(t, tlsCfg.Certificates) } else { - // TLS parameter objects are opaque. Just check if they were created. + // TLSCertificate parameter objects are opaque. Just check if they were created. // There is no trivial way to inspect their metadata. Trust me :) assert.NotNil(t, tlsCfg.RootCAs) assert.Equal(t, tt.caCertOnly, tlsCfg.Certificates == nil) diff --git a/schemas/analyzer-troubleshoot-v1beta2.json b/schemas/analyzer-troubleshoot-v1beta2.json index 13504a473..e7cb96a3a 100644 --- a/schemas/analyzer-troubleshoot-v1beta2.json +++ b/schemas/analyzer-troubleshoot-v1beta2.json @@ -4808,6 +4808,82 @@ } } }, + "tlsCertificate": { + "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"}] + } + } + }, "udpPortStatus": { "type": "object", "required": [ diff --git a/schemas/collector-troubleshoot-v1beta2.json b/schemas/collector-troubleshoot-v1beta2.json index 46838fc23..8e103c29c 100644 --- a/schemas/collector-troubleshoot-v1beta2.json +++ b/schemas/collector-troubleshoot-v1beta2.json @@ -15483,6 +15483,29 @@ } } }, + "tlsCertificate": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "type": "string" + }, + "collectorName": { + "type": "string" + }, + "exclude": { + "oneOf": [{"type": "string"},{"type": "boolean"}] + }, + "expectedCertSubpath": { + "type": "string" + }, + "httpsProxy": { + "type": "string" + } + } + }, "udpPortStatus": { "type": "object", "required": [ diff --git a/schemas/supportbundle-troubleshoot-v1beta2.json b/schemas/supportbundle-troubleshoot-v1beta2.json index e6bfa932e..840c1df86 100644 --- a/schemas/supportbundle-troubleshoot-v1beta2.json +++ b/schemas/supportbundle-troubleshoot-v1beta2.json @@ -19399,6 +19399,82 @@ } } }, + "tlsCertificate": { + "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"}] + } + } + }, "udpPortStatus": { "type": "object", "required": [ @@ -20353,6 +20429,29 @@ } } }, + "tlsCertificate": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "type": "string" + }, + "collectorName": { + "type": "string" + }, + "exclude": { + "oneOf": [{"type": "string"},{"type": "boolean"}] + }, + "expectedCertSubpath": { + "type": "string" + }, + "httpsProxy": { + "type": "string" + } + } + }, "udpPortStatus": { "type": "object", "required": [ diff --git a/test/e2e/preflight/spec/localHostCollectors.yaml b/test/e2e/preflight/spec/localHostCollectors.yaml index 701a054b9..daf715d28 100644 --- a/test/e2e/preflight/spec/localHostCollectors.yaml +++ b/test/e2e/preflight/spec/localHostCollectors.yaml @@ -5,6 +5,9 @@ metadata: spec: collectors: - cpu: {} + - tlsCertificate: + address: "replicated.app:443" + collectorName: tls analyzers: - cpu: checkName: "Number of CPUs" @@ -17,3 +20,12 @@ spec: message: At least 4 CPU cores are recommended - pass: message: This server has at least 4 CPU cores + - tlsCertificate: + checkName: "replicated.app TLS certificate" + collectorName: tls + outcomes: + - pass: + when: "issuer == E5" + message: The TLS certificate for replicated.app is issued by E5 (letsencrypt) + - fail: + message: The TLS certificate for replicated.app is not issued by E5 (letsencrypt) diff --git a/test/e2e/support-bundle/spec/localHostCollectors.yaml b/test/e2e/support-bundle/spec/localHostCollectors.yaml index 2e4951a84..59d46b857 100644 --- a/test/e2e/support-bundle/spec/localHostCollectors.yaml +++ b/test/e2e/support-bundle/spec/localHostCollectors.yaml @@ -32,3 +32,6 @@ spec: - run: collectorName: uptime command: uptime + - tls: + address: "replicated.app:443" + collectorName: tls