From 843afb60ea5f390078a76d0de2a8548ac235a143 Mon Sep 17 00:00:00 2001 From: Andrew Lavery Date: Mon, 31 Mar 2025 17:55:07 -0500 Subject: [PATCH 01/20] add a new collector that gets information about a server's TLS certificates --- examples/sdk/helm-template/go.mod | 26 +-- examples/sdk/helm-template/go.sum | 13 ++ .../v1beta2/hostcollector_shared.go | 7 + .../v1beta2/zz_generated.deepcopy.go | 21 ++ pkg/collect/host_tls.go | 101 +++++++++ pkg/collect/host_tls_test.go | 203 ++++++++++++++++++ 6 files changed, 358 insertions(+), 13 deletions(-) create mode 100644 pkg/collect/host_tls.go create mode 100644 pkg/collect/host_tls_test.go diff --git a/examples/sdk/helm-template/go.mod b/examples/sdk/helm-template/go.mod index 569f74709..9b9d60646 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.33.0 // indirect - golang.org/x/net v0.35.0 // indirect + golang.org/x/crypto v0.36.0 // indirect + golang.org/x/net v0.37.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 e0a319567..bb8c9d70b 100644 --- a/examples/sdk/helm-template/go.sum +++ b/examples/sdk/helm-template/go.sum @@ -91,6 +91,7 @@ github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp 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/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= @@ -113,6 +114,7 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus= golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= +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= @@ -121,6 +123,7 @@ golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= +golang.org/x/net v0.37.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= @@ -131,14 +134,18 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w 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/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU= golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s= +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/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= 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/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= @@ -164,18 +171,23 @@ 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= +helm.sh/helm/v3 v3.17.2/go.mod h1:+uJKMH/UiMzZQOALR3XUf3BLIoczI2RKKD6bMhPh4G8= k8s.io/api v0.32.1 h1:f562zw9cy+GvXzXf0CKlVQ7yHJVYzLfL6JAS4kOAaOc= k8s.io/api v0.32.1/go.mod h1:/Yi/BqkuueW1BgpoePYBRdDYfjPF5sgTr5+YqDZra5k= k8s.io/api v0.32.2/go.mod h1:hKlhk4x1sJyYnHENsrdCWw31FEmCijNGPJO5WzHiJ6Y= +k8s.io/api v0.32.3/go.mod h1:2wEDTXADtm/HA7CCMD8D8bK4yuBUptzaRhYcYEEYA3k= k8s.io/apiextensions-apiserver v0.32.1 h1:hjkALhRUeCariC8DiVmb5jj0VjIc1N0DREP32+6UXZw= k8s.io/apiextensions-apiserver v0.32.1/go.mod h1:sxWIGuGiYov7Io1fAS2X06NjMIk5CbRHc2StSmbaQto= k8s.io/apiextensions-apiserver v0.32.2/go.mod h1:GPwf8sph7YlJT3H6aKUWtd0E+oyShk/YHWQHf/OOgCA= +k8s.io/apiextensions-apiserver v0.32.3/go.mod h1:8YwcvVRMVzw0r1Stc7XfGAzB/SIVLunqApySV5V7Dss= k8s.io/apimachinery v0.32.1 h1:683ENpaCBjma4CYqsmZyhEzrGz6cjn1MY/X2jB2hkZs= k8s.io/apimachinery v0.32.1/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE= k8s.io/apimachinery v0.32.2/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE= +k8s.io/apimachinery v0.32.3/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE= k8s.io/client-go v0.32.1 h1:otM0AxdhdBIaQh7l1Q0jQpmo7WOFIk5FFa4bg6YMdUU= k8s.io/client-go v0.32.1/go.mod h1:aTTKZY7MdxUaJ/KiUs8D+GssR9zJZi77ZqtzcGXIiDg= k8s.io/client-go v0.32.2/go.mod h1:fpZ4oJXclZ3r2nDOv+Ux3XcJutfrwjKTCHz2H3sww94= +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= @@ -185,6 +197,7 @@ k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738/go.mod h1:OLgZIPagt7ERELqWJFomSt sigs.k8s.io/controller-runtime v0.20.1 h1:JbGMAG/X94NeM3xvjenVUaBjy6Ui4Ogd/J5ZtjZnHaE= sigs.k8s.io/controller-runtime v0.20.1/go.mod h1:BrP3w158MwvB3ZbNpaAcIKkHQ7YGpYnzpoSTZ8E14WU= sigs.k8s.io/controller-runtime v0.20.2/go.mod h1:xg2XB0K5ShQzAgsoujxuKN4LNXR2LfwwHsPj7Iaw+XY= +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/apis/troubleshoot/v1beta2/hostcollector_shared.go b/pkg/apis/troubleshoot/v1beta2/hostcollector_shared.go index 383d6102a..9e5f3c65e 100644 --- a/pkg/apis/troubleshoot/v1beta2/hostcollector_shared.go +++ b/pkg/apis/troubleshoot/v1beta2/hostcollector_shared.go @@ -235,6 +235,12 @@ type HostSysctl struct { HostCollectorMeta `json:",inline" yaml:",inline"` } +type HostTLS struct { + HostCollectorMeta `json:",inline" yaml:",inline"` + Address string `json:"address"` + ExpectedIssuer string `json:"expectedIssuer,omitempty"` +} + type HostCollect struct { CPU *CPU `json:"cpu,omitempty" yaml:"cpu,omitempty"` Memory *Memory `json:"memory,omitempty" yaml:"memory,omitempty"` @@ -265,6 +271,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"` + HostTLS *HostTLS `json:"tls,omitempty" yaml:"tls,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..2cc32e388 100644 --- a/pkg/apis/troubleshoot/v1beta2/zz_generated.deepcopy.go +++ b/pkg/apis/troubleshoot/v1beta2/zz_generated.deepcopy.go @@ -2213,6 +2213,11 @@ func (in *HostCollect) DeepCopyInto(out *HostCollect) { *out = new(HostSysctl) (*in).DeepCopyInto(*out) } + if in.HostTLS != nil { + in, out := &in.HostTLS, &out.HostTLS + *out = new(HostTLS) + (*in).DeepCopyInto(*out) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HostCollect. @@ -2903,6 +2908,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 *HostTLS) DeepCopyInto(out *HostTLS) { + *out = *in + in.HostCollectorMeta.DeepCopyInto(&out.HostCollectorMeta) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HostTLS. +func (in *HostTLS) DeepCopy() *HostTLS { + if in == nil { + return nil + } + out := new(HostTLS) + 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 diff --git a/pkg/collect/host_tls.go b/pkg/collect/host_tls.go new file mode 100644 index 000000000..b86217ac1 --- /dev/null +++ b/pkg/collect/host_tls.go @@ -0,0 +1,101 @@ +package collect + +import ( + "bytes" + "crypto/tls" + "encoding/json" + "log" + "path/filepath" + "time" + + "github.com/pkg/errors" + troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2" +) + +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"` + MatchesExpectedIssuer bool `json:"matches_expected_issuer"` +} + +type CollectHostTLS struct { + hostCollector *troubleshootv1beta2.HostTLS + BundlePath string +} + +func (c *CollectHostTLS) Title() string { + return hostCollectorTitleOrDefault(c.hostCollector.HostCollectorMeta, "TCP Port Status") +} + +func (c *CollectHostTLS) IsExcluded() (bool, error) { + return isExcluded(c.hostCollector.Exclude) +} + +func (c *CollectHostTLS) Collect(progressChan chan<- interface{}) (map[string][]byte, error) { + tlsInfo := TLSInfo{} + + conf := &tls.Config{ + InsecureSkipVerify: true, + } + + conn, err := tls.Dial("tcp", c.hostCollector.Address, conf) + if err != nil { + log.Println("Error in Dial", err) + return nil, errors.Wrap(err, "failed to dial tls") + } + defer conn.Close() + certs := conn.ConnectionState().PeerCertificates + cleanedCerts := make([]CertInfo, len(certs)) + for i, cert := range certs { + cleanedCerts[i] = 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.ExpectedIssuer != "" { + if len(certs) == 0 { + tlsInfo.MatchesExpectedIssuer = false + } else { + tlsInfo.MatchesExpectedIssuer = c.hostCollector.ExpectedIssuer == certs[0].Issuer.CommonName + } + } + + b, err := json.Marshal(tlsInfo) + if err != nil { + return nil, errors.Wrap(err, "failed to marshal tls info") + } + + output := NewResult() + output.SaveResult(c.BundlePath, HostTimePath, bytes.NewBuffer(b)) + + collectorName := c.hostCollector.CollectorName + if collectorName == "" { + collectorName = "result" + } + name := filepath.Join("host-collectors/tls", collectorName+".json") + + return map[string][]byte{ + name: b, + }, nil +} + +func (c *CollectHostTLS) RemoteCollect(progressChan chan<- interface{}) (map[string][]byte, error) { + return nil, ErrRemoteCollectorNotImplemented +} diff --git a/pkg/collect/host_tls_test.go b/pkg/collect/host_tls_test.go new file mode 100644 index 000000000..30bfba3c0 --- /dev/null +++ b/pkg/collect/host_tls_test.go @@ -0,0 +1,203 @@ +package collect + +import ( + "crypto/rand" + "crypto/rsa" + "crypto/tls" + "crypto/x509" + "crypto/x509/pkix" + "encoding/json" + "encoding/pem" + "math/big" + "net/http" + "net/http/httptest" + "os" + "path/filepath" + "strings" + "testing" + "time" + + "github.com/pkg/errors" + 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 TLS 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) + + tests := []struct { + name string + hostCollector *troubleshootv1beta2.HostTLS + issuerMatches bool + wantErr bool + }{ + { + name: "successful collection with matching issuer", + hostCollector: &troubleshootv1beta2.HostTLS{ + Address: serverAddr, + ExpectedIssuer: "localhost", + HostCollectorMeta: troubleshootv1beta2.HostCollectorMeta{ + CollectorName: "test-tls", + }, + }, + issuerMatches: true, + wantErr: false, + }, + { + name: "successful collection with non-matching issuer", + hostCollector: &troubleshootv1beta2.HostTLS{ + Address: serverAddr, + ExpectedIssuer: "Different Issuer", + HostCollectorMeta: troubleshootv1beta2.HostCollectorMeta{ + CollectorName: "test-tls-wrong-issuer", + }, + }, + issuerMatches: false, + wantErr: false, + }, + { + name: "successful collection without expected issuer", + hostCollector: &troubleshootv1beta2.HostTLS{ + Address: serverAddr, + HostCollectorMeta: troubleshootv1beta2.HostCollectorMeta{ + CollectorName: "test-tls-no-issuer", + }, + }, + issuerMatches: false, + wantErr: false, + }, + { + name: "failed connection", + hostCollector: &troubleshootv1beta2.HostTLS{ + Address: "invalid-address:9999", + HostCollectorMeta: troubleshootv1beta2.HostCollectorMeta{ + CollectorName: "test-tls-failed", + }, + }, + issuerMatches: false, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &CollectHostTLS{ + hostCollector: tt.hostCollector, + BundlePath: bundlePath, + } + + collected, err := c.Collect(nil) + if tt.wantErr { + require.Error(t, err) + return + } + + require.NoError(t, err) + require.NotNil(t, collected) + + expectedFilename := filepath.Join("host-collectors/tls", tt.hostCollector.CollectorName+".json") + assert.Contains(t, collected, expectedFilename) + + // Validate the content + var tlsInfo TLSInfo + err = json.Unmarshal(collected[expectedFilename], &tlsInfo) + + require.NoError(t, err) + + // Verify we have certificate information + require.NotEmpty(t, tlsInfo.PeerCertificates) + + // If expected issuer is set, verify the match status + require.Equal(t, tt.issuerMatches, tlsInfo.MatchesExpectedIssuer) + }) + } +} + +// 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 TLS 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) { + w.WriteHeader(http.StatusOK) + w.Write([]byte("TLS Test Server")) + }) + + // Use httptest package to create a TLS 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 +} From d57b21228cdb9e98593289265198afee583fbdce Mon Sep 17 00:00:00 2001 From: Andrew Lavery Date: Mon, 31 Mar 2025 22:27:22 -0500 Subject: [PATCH 02/20] check issuer in analyzer, update test --- .../v1beta2/hostcollector_shared.go | 1 - pkg/collect/host_tls.go | 11 +--- pkg/collect/host_tls_test.go | 56 ++++++++----------- 3 files changed, 25 insertions(+), 43 deletions(-) diff --git a/pkg/apis/troubleshoot/v1beta2/hostcollector_shared.go b/pkg/apis/troubleshoot/v1beta2/hostcollector_shared.go index 9e5f3c65e..fd9b311db 100644 --- a/pkg/apis/troubleshoot/v1beta2/hostcollector_shared.go +++ b/pkg/apis/troubleshoot/v1beta2/hostcollector_shared.go @@ -238,7 +238,6 @@ type HostSysctl struct { type HostTLS struct { HostCollectorMeta `json:",inline" yaml:",inline"` Address string `json:"address"` - ExpectedIssuer string `json:"expectedIssuer,omitempty"` } type HostCollect struct { diff --git a/pkg/collect/host_tls.go b/pkg/collect/host_tls.go index b86217ac1..6da004ba5 100644 --- a/pkg/collect/host_tls.go +++ b/pkg/collect/host_tls.go @@ -23,8 +23,7 @@ type CertInfo struct { } type TLSInfo struct { - PeerCertificates []CertInfo `json:"peer_certificates"` - MatchesExpectedIssuer bool `json:"matches_expected_issuer"` + PeerCertificates []CertInfo `json:"peer_certificates"` } type CollectHostTLS struct { @@ -69,14 +68,6 @@ func (c *CollectHostTLS) Collect(progressChan chan<- interface{}) (map[string][] tlsInfo.PeerCertificates = cleanedCerts - if c.hostCollector.ExpectedIssuer != "" { - if len(certs) == 0 { - tlsInfo.MatchesExpectedIssuer = false - } else { - tlsInfo.MatchesExpectedIssuer = c.hostCollector.ExpectedIssuer == certs[0].Issuer.CommonName - } - } - b, err := json.Marshal(tlsInfo) if err != nil { return nil, errors.Wrap(err, "failed to marshal tls info") diff --git a/pkg/collect/host_tls_test.go b/pkg/collect/host_tls_test.go index 30bfba3c0..9588f22f0 100644 --- a/pkg/collect/host_tls_test.go +++ b/pkg/collect/host_tls_test.go @@ -43,46 +43,34 @@ func TestCollectHostTLS_Collect(t *testing.T) { 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.HostTLS - issuerMatches bool + certFields []certFields wantErr bool }{ { - name: "successful collection with matching issuer", + name: "successful collection", hostCollector: &troubleshootv1beta2.HostTLS{ - Address: serverAddr, - ExpectedIssuer: "localhost", + Address: serverAddr, HostCollectorMeta: troubleshootv1beta2.HostCollectorMeta{ CollectorName: "test-tls", }, }, - issuerMatches: true, - wantErr: false, - }, - { - name: "successful collection with non-matching issuer", - hostCollector: &troubleshootv1beta2.HostTLS{ - Address: serverAddr, - ExpectedIssuer: "Different Issuer", - HostCollectorMeta: troubleshootv1beta2.HostCollectorMeta{ - CollectorName: "test-tls-wrong-issuer", - }, - }, - issuerMatches: false, - wantErr: false, - }, - { - name: "successful collection without expected issuer", - hostCollector: &troubleshootv1beta2.HostTLS{ - Address: serverAddr, - HostCollectorMeta: troubleshootv1beta2.HostCollectorMeta{ - CollectorName: "test-tls-no-issuer", + certFields: []certFields{ + { + Issuer: "localhost", + Subject: "localhost", + IsCA: false, }, }, - issuerMatches: false, - wantErr: false, + wantErr: false, }, { name: "failed connection", @@ -92,8 +80,7 @@ func TestCollectHostTLS_Collect(t *testing.T) { CollectorName: "test-tls-failed", }, }, - issuerMatches: false, - wantErr: true, + wantErr: true, }, } @@ -119,14 +106,19 @@ func TestCollectHostTLS_Collect(t *testing.T) { // Validate the content var tlsInfo TLSInfo err = json.Unmarshal(collected[expectedFilename], &tlsInfo) - + require.NoError(t, err) // Verify we have certificate information require.NotEmpty(t, tlsInfo.PeerCertificates) - // If expected issuer is set, verify the match status - require.Equal(t, tt.issuerMatches, tlsInfo.MatchesExpectedIssuer) + // Verify the certificate fields match the expected values + require.Equal(t, len(tt.certFields), len(tlsInfo.PeerCertificates)) + for i, cert := range tlsInfo.PeerCertificates { + assert.Equal(t, tt.certFields[i].Issuer, cert.Issuer) + assert.Equal(t, tt.certFields[i].Subject, cert.Subject) + assert.Equal(t, tt.certFields[i].IsCA, cert.IsCA) + } }) } } From 8475f6ed8c4fdec54b141e88d917d8b27644d48c Mon Sep 17 00:00:00 2001 From: Andrew Lavery Date: Tue, 1 Apr 2025 10:07:50 -0500 Subject: [PATCH 03/20] add analyzer and unit tests --- pkg/analyze/host_tls.go | 86 +++++++++ pkg/analyze/host_tls_test.go | 173 ++++++++++++++++++ pkg/analyze/types/restic_types.go | 2 +- pkg/analyze/types/tls_types.go | 15 ++ .../v1beta2/hostanalyzer_shared.go | 6 + pkg/collect/host_tls.go | 21 +-- pkg/collect/host_tls_test.go | 3 +- 7 files changed, 287 insertions(+), 19 deletions(-) create mode 100644 pkg/analyze/host_tls.go create mode 100644 pkg/analyze/host_tls_test.go create mode 100644 pkg/analyze/types/tls_types.go diff --git a/pkg/analyze/host_tls.go b/pkg/analyze/host_tls.go new file mode 100644 index 000000000..6ca719e16 --- /dev/null +++ b/pkg/analyze/host_tls.go @@ -0,0 +1,86 @@ +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 AnalyzeHostTLS struct { + hostAnalyzer *troubleshootv1beta2.TLSAnalyze +} + +func (a *AnalyzeHostTLS) Title() string { + return hostAnalyzerTitleOrDefault(a.hostAnalyzer.AnalyzeMeta, "TLS") +} + +func (a *AnalyzeHostTLS) IsExcluded() (bool, error) { + return isExcluded(a.hostAnalyzer.Exclude) +} + +func (a *AnalyzeHostTLS) Analyze( + getCollectedFileContents func(string) ([]byte, error), findFiles getChildCollectedFileContents, +) ([]*AnalyzeResult, error) { + + collectorName := a.hostAnalyzer.CollectorName + if collectorName == "" { + collectorName = "result" + } + + const nodeBaseDir = "host-collectors/tls" + 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 *AnalyzeHostTLS) 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, " ") + if len(parts) < 3 { + return false, fmt.Errorf("invalid when clause: %s", when) + } + + checkType := parts[0] + if checkType != "issuer" { + return false, fmt.Errorf("invalid check type: %s", checkType) + } + + issuer := strings.Join(parts[2:], " ") + + for _, cert := range tlsInfo.PeerCertificates { + if cert.Issuer == issuer { + return true, nil + } + } + + return false, nil +} diff --git a/pkg/analyze/host_tls_test.go b/pkg/analyze/host_tls_test.go new file mode 100644 index 000000000..8ac6fa0d9 --- /dev/null +++ b/pkg/analyze/host_tls_test.go @@ -0,0 +1,173 @@ +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{ + 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: "TLS", + IsPass: true, + Message: "issuer was foo", + }, + }, + }, + + { + name: "invalid check type", + tlsInfo: &types.TLSInfo{ + PeerCertificates: []types.CertInfo{ + { + Issuer: "foo", + }, + }, + }, + hostAnalyzer: &troubleshootv1beta2.TLSAnalyze{ + 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{ + 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: "TLS", + IsFail: true, + Message: "issuer was not abc", + }, + }, + }, + { + name: "second cert matches", + tlsInfo: &types.TLSInfo{ + PeerCertificates: []types.CertInfo{ + { + Issuer: "foo", + }, + { + Issuer: "bar", + }, + }, + }, + hostAnalyzer: &troubleshootv1beta2.TLSAnalyze{ + 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: "TLS", + IsPass: true, + Message: "issuer was bar", + }, + }, + }, + } + 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 := (&AnalyzeHostTLS{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..9d7a339b6 --- /dev/null +++ b/pkg/analyze/types/tls_types.go @@ -0,0 +1,15 @@ +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"` +} diff --git a/pkg/apis/troubleshoot/v1beta2/hostanalyzer_shared.go b/pkg/apis/troubleshoot/v1beta2/hostanalyzer_shared.go index 1b1e8cd51..03825515f 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"` diff --git a/pkg/collect/host_tls.go b/pkg/collect/host_tls.go index 6da004ba5..0ae07f3ad 100644 --- a/pkg/collect/host_tls.go +++ b/pkg/collect/host_tls.go @@ -9,23 +9,10 @@ import ( "time" "github.com/pkg/errors" + "github.com/replicatedhq/troubleshoot/pkg/analyze/types" troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2" ) -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"` -} - type CollectHostTLS struct { hostCollector *troubleshootv1beta2.HostTLS BundlePath string @@ -40,7 +27,7 @@ func (c *CollectHostTLS) IsExcluded() (bool, error) { } func (c *CollectHostTLS) Collect(progressChan chan<- interface{}) (map[string][]byte, error) { - tlsInfo := TLSInfo{} + tlsInfo := types.TLSInfo{} conf := &tls.Config{ InsecureSkipVerify: true, @@ -53,9 +40,9 @@ func (c *CollectHostTLS) Collect(progressChan chan<- interface{}) (map[string][] } defer conn.Close() certs := conn.ConnectionState().PeerCertificates - cleanedCerts := make([]CertInfo, len(certs)) + cleanedCerts := make([]types.CertInfo, len(certs)) for i, cert := range certs { - cleanedCerts[i] = CertInfo{ + cleanedCerts[i] = types.CertInfo{ Issuer: cert.Issuer.CommonName, Subject: cert.Subject.CommonName, Serial: cert.SerialNumber.String(), diff --git a/pkg/collect/host_tls_test.go b/pkg/collect/host_tls_test.go index 9588f22f0..65f6a432d 100644 --- a/pkg/collect/host_tls_test.go +++ b/pkg/collect/host_tls_test.go @@ -18,6 +18,7 @@ import ( "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" @@ -104,7 +105,7 @@ func TestCollectHostTLS_Collect(t *testing.T) { assert.Contains(t, collected, expectedFilename) // Validate the content - var tlsInfo TLSInfo + var tlsInfo types.TLSInfo err = json.Unmarshal(collected[expectedFilename], &tlsInfo) require.NoError(t, err) From 92a599a9f5d6d942cff664ad727c340f46f18f98 Mon Sep 17 00:00:00 2001 From: Andrew Lavery Date: Tue, 1 Apr 2025 10:13:29 -0500 Subject: [PATCH 04/20] make schemas --- config/crds/troubleshoot.sh_collectors.yaml | 11 +++++++++++ config/crds/troubleshoot.sh_hostcollectors.yaml | 11 +++++++++++ config/crds/troubleshoot.sh_hostpreflights.yaml | 11 +++++++++++ config/crds/troubleshoot.sh_supportbundles.yaml | 11 +++++++++++ schemas/collector-troubleshoot-v1beta2.json | 17 +++++++++++++++++ schemas/supportbundle-troubleshoot-v1beta2.json | 17 +++++++++++++++++ 6 files changed, 78 insertions(+) diff --git a/config/crds/troubleshoot.sh_collectors.yaml b/config/crds/troubleshoot.sh_collectors.yaml index 292e2196b..fe84c99f1 100644 --- a/config/crds/troubleshoot.sh_collectors.yaml +++ b/config/crds/troubleshoot.sh_collectors.yaml @@ -17744,6 +17744,17 @@ spec: exclude: type: BoolString type: object + tls: + properties: + address: + type: string + collectorName: + type: string + exclude: + type: BoolString + 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..14d11a66a 100644 --- a/config/crds/troubleshoot.sh_hostcollectors.yaml +++ b/config/crds/troubleshoot.sh_hostcollectors.yaml @@ -1970,6 +1970,17 @@ spec: exclude: type: BoolString type: object + tls: + properties: + address: + type: string + collectorName: + type: string + exclude: + type: BoolString + 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..d4ccb5dac 100644 --- a/config/crds/troubleshoot.sh_hostpreflights.yaml +++ b/config/crds/troubleshoot.sh_hostpreflights.yaml @@ -1970,6 +1970,17 @@ spec: exclude: type: BoolString type: object + tls: + properties: + address: + type: string + collectorName: + type: string + exclude: + type: BoolString + 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..2b7c6d0f2 100644 --- a/config/crds/troubleshoot.sh_supportbundles.yaml +++ b/config/crds/troubleshoot.sh_supportbundles.yaml @@ -20912,6 +20912,17 @@ spec: exclude: type: BoolString type: object + tls: + properties: + address: + type: string + collectorName: + type: string + exclude: + type: BoolString + required: + - address + type: object udpPortStatus: properties: collectorName: diff --git a/schemas/collector-troubleshoot-v1beta2.json b/schemas/collector-troubleshoot-v1beta2.json index 46838fc23..ce8c68f47 100644 --- a/schemas/collector-troubleshoot-v1beta2.json +++ b/schemas/collector-troubleshoot-v1beta2.json @@ -15483,6 +15483,23 @@ } } }, + "tls": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "type": "string" + }, + "collectorName": { + "type": "string" + }, + "exclude": { + "oneOf": [{"type": "string"},{"type": "boolean"}] + } + } + }, "udpPortStatus": { "type": "object", "required": [ diff --git a/schemas/supportbundle-troubleshoot-v1beta2.json b/schemas/supportbundle-troubleshoot-v1beta2.json index e6bfa932e..3a4e485fc 100644 --- a/schemas/supportbundle-troubleshoot-v1beta2.json +++ b/schemas/supportbundle-troubleshoot-v1beta2.json @@ -20353,6 +20353,23 @@ } } }, + "tls": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "type": "string" + }, + "collectorName": { + "type": "string" + }, + "exclude": { + "oneOf": [{"type": "string"},{"type": "boolean"}] + } + } + }, "udpPortStatus": { "type": "object", "required": [ From 70a099a931e13add58bca2e1984de9f4cd51522a Mon Sep 17 00:00:00 2001 From: Andrew Lavery Date: Tue, 1 Apr 2025 10:25:12 -0500 Subject: [PATCH 05/20] add replicated.app analyze test --- pkg/analyze/host_tls_test.go | 47 ++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/pkg/analyze/host_tls_test.go b/pkg/analyze/host_tls_test.go index 8ac6fa0d9..bdb43d020 100644 --- a/pkg/analyze/host_tls_test.go +++ b/pkg/analyze/host_tls_test.go @@ -147,6 +147,53 @@ func TestAnalyzeHostTLS(t *testing.T) { }, }, }, + { + 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{ + 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: "TLS", + IsPass: true, + Message: "The issuer for replicated.app is E5 (letsencrypt) as expected", + }, + }, + }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { From 9aba0860dd36d63d10d9fa03551d3a906a600c34 Mon Sep 17 00:00:00 2001 From: Andrew Lavery Date: Tue, 1 Apr 2025 10:29:24 -0500 Subject: [PATCH 06/20] make generate --- .../v1beta2/zz_generated.deepcopy.go | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/pkg/apis/troubleshoot/v1beta2/zz_generated.deepcopy.go b/pkg/apis/troubleshoot/v1beta2/zz_generated.deepcopy.go index 2cc32e388..73ee91855 100644 --- a/pkg/apis/troubleshoot/v1beta2/zz_generated.deepcopy.go +++ b/pkg/apis/troubleshoot/v1beta2/zz_generated.deepcopy.go @@ -5246,6 +5246,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 From 5fccca2cdc008d61a33ed0a0a855b6e2cf911ae3 Mon Sep 17 00:00:00 2001 From: Andrew Lavery Date: Tue, 1 Apr 2025 10:34:44 -0500 Subject: [PATCH 07/20] go mod tidy examples --- examples/sdk/helm-template/go.mod | 2 +- examples/sdk/helm-template/go.sum | 60 +++++++++---------------------- 2 files changed, 17 insertions(+), 45 deletions(-) diff --git a/examples/sdk/helm-template/go.mod b/examples/sdk/helm-template/go.mod index 9b9d60646..04e4710fd 100644 --- a/examples/sdk/helm-template/go.mod +++ b/examples/sdk/helm-template/go.mod @@ -53,7 +53,7 @@ require ( github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect github.com/xeipuuv/gojsonschema v1.2.0 // indirect golang.org/x/crypto v0.36.0 // indirect - golang.org/x/net v0.37.0 // indirect + golang.org/x/net v0.38.0 // indirect golang.org/x/oauth2 v0.25.0 // indirect golang.org/x/sys v0.31.0 // indirect golang.org/x/term v0.30.0 // indirect diff --git a/examples/sdk/helm-template/go.sum b/examples/sdk/helm-template/go.sum index f1c615499..3853f96cd 100644 --- a/examples/sdk/helm-template/go.sum +++ b/examples/sdk/helm-template/go.sum @@ -88,8 +88,7 @@ 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= @@ -111,22 +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.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus= -golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= +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/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs= -golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ= 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.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= -golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= -golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= -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= @@ -135,26 +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/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU= -golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s= +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/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= -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/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= @@ -172,31 +161,15 @@ 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= +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.1 h1:f562zw9cy+GvXzXf0CKlVQ7yHJVYzLfL6JAS4kOAaOc= -k8s.io/api v0.32.1/go.mod h1:/Yi/BqkuueW1BgpoePYBRdDYfjPF5sgTr5+YqDZra5k= -k8s.io/api v0.32.2/go.mod h1:hKlhk4x1sJyYnHENsrdCWw31FEmCijNGPJO5WzHiJ6Y= +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.1 h1:hjkALhRUeCariC8DiVmb5jj0VjIc1N0DREP32+6UXZw= -k8s.io/apiextensions-apiserver v0.32.1/go.mod h1:sxWIGuGiYov7Io1fAS2X06NjMIk5CbRHc2StSmbaQto= -k8s.io/apiextensions-apiserver v0.32.2/go.mod h1:GPwf8sph7YlJT3H6aKUWtd0E+oyShk/YHWQHf/OOgCA= +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.1 h1:683ENpaCBjma4CYqsmZyhEzrGz6cjn1MY/X2jB2hkZs= -k8s.io/apimachinery v0.32.1/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE= -k8s.io/apimachinery v0.32.2/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE= +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.1 h1:otM0AxdhdBIaQh7l1Q0jQpmo7WOFIk5FFa4bg6YMdUU= -k8s.io/client-go v0.32.1/go.mod h1:aTTKZY7MdxUaJ/KiUs8D+GssR9zJZi77ZqtzcGXIiDg= -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= +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= @@ -204,8 +177,7 @@ k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f h1:GA7//TjRY9yWGy1poLzYYJ 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= From 6df1106472d08557ca7d3a79c672c3a31f0c1f9b Mon Sep 17 00:00:00 2001 From: Andrew Lavery Date: Tue, 1 Apr 2025 12:01:00 -0500 Subject: [PATCH 08/20] Update pkg/analyze/host_tls.go Co-authored-by: Ethan Mosbaugh --- pkg/analyze/host_tls.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/analyze/host_tls.go b/pkg/analyze/host_tls.go index 6ca719e16..1fe95a77e 100644 --- a/pkg/analyze/host_tls.go +++ b/pkg/analyze/host_tls.go @@ -64,7 +64,7 @@ func (a *AnalyzeHostTLS) CheckCondition(when string, data []byte) (bool, error) // currently this supports only checks like "issuer == foo" func compareHostTLSResult(when string, tlsInfo *types.TLSInfo) (bool, error) { - parts := strings.Split(when, " ") + parts := strings.SplitN(when, " ", 3) if len(parts) < 3 { return false, fmt.Errorf("invalid when clause: %s", when) } @@ -74,7 +74,7 @@ func compareHostTLSResult(when string, tlsInfo *types.TLSInfo) (bool, error) { return false, fmt.Errorf("invalid check type: %s", checkType) } - issuer := strings.Join(parts[2:], " ") + issuer := parts[2] for _, cert := range tlsInfo.PeerCertificates { if cert.Issuer == issuer { From e83266412182d495a3424cbd4d70e1e8e0882987 Mon Sep 17 00:00:00 2001 From: Andrew Lavery Date: Tue, 1 Apr 2025 12:11:43 -0500 Subject: [PATCH 09/20] include error in collect results instead of returning --- pkg/analyze/types/tls_types.go | 1 + pkg/collect/host_tls.go | 38 +++++++++++++++++----------------- pkg/collect/host_tls_test.go | 10 ++++----- 3 files changed, 25 insertions(+), 24 deletions(-) diff --git a/pkg/analyze/types/tls_types.go b/pkg/analyze/types/tls_types.go index 9d7a339b6..07a4fc2aa 100644 --- a/pkg/analyze/types/tls_types.go +++ b/pkg/analyze/types/tls_types.go @@ -12,4 +12,5 @@ type CertInfo struct { type TLSInfo struct { PeerCertificates []CertInfo `json:"peer_certificates"` + Error string `json:"error"` } diff --git a/pkg/collect/host_tls.go b/pkg/collect/host_tls.go index 0ae07f3ad..4f9ede3e7 100644 --- a/pkg/collect/host_tls.go +++ b/pkg/collect/host_tls.go @@ -4,8 +4,8 @@ import ( "bytes" "crypto/tls" "encoding/json" - "log" "path/filepath" + "strings" "time" "github.com/pkg/errors" @@ -35,25 +35,25 @@ func (c *CollectHostTLS) Collect(progressChan chan<- interface{}) (map[string][] conn, err := tls.Dial("tcp", c.hostCollector.Address, conf) if err != nil { - log.Println("Error in Dial", err) - return nil, errors.Wrap(err, "failed to dial tls") - } - defer conn.Close() - certs := conn.ConnectionState().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.Error = err.Error() + } else { + defer conn.Close() + certs := conn.ConnectionState().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 + tlsInfo.PeerCertificates = cleanedCerts + } b, err := json.Marshal(tlsInfo) if err != nil { @@ -65,7 +65,7 @@ func (c *CollectHostTLS) Collect(progressChan chan<- interface{}) (map[string][] collectorName := c.hostCollector.CollectorName if collectorName == "" { - collectorName = "result" + collectorName = strings.ReplaceAll(c.hostCollector.Address, ":", "-") } name := filepath.Join("host-collectors/tls", collectorName+".json") diff --git a/pkg/collect/host_tls_test.go b/pkg/collect/host_tls_test.go index 65f6a432d..3c4302b4a 100644 --- a/pkg/collect/host_tls_test.go +++ b/pkg/collect/host_tls_test.go @@ -93,11 +93,6 @@ func TestCollectHostTLS_Collect(t *testing.T) { } collected, err := c.Collect(nil) - if tt.wantErr { - require.Error(t, err) - return - } - require.NoError(t, err) require.NotNil(t, collected) @@ -110,6 +105,11 @@ func TestCollectHostTLS_Collect(t *testing.T) { require.NoError(t, err) + if tt.wantErr { + require.NotNil(t, tlsInfo.Error) + return + } + // Verify we have certificate information require.NotEmpty(t, tlsInfo.PeerCertificates) From 33c4487958c9d4d4dd86786593838f5f43b671b7 Mon Sep 17 00:00:00 2001 From: Andrew Lavery Date: Tue, 1 Apr 2025 12:16:35 -0500 Subject: [PATCH 10/20] add to struct, make generate schemas --- config/crds/troubleshoot.sh_analyzers.yaml | 49 ++++++++++++ .../crds/troubleshoot.sh_hostcollectors.yaml | 49 ++++++++++++ .../crds/troubleshoot.sh_hostpreflights.yaml | 49 ++++++++++++ .../crds/troubleshoot.sh_supportbundles.yaml | 49 ++++++++++++ .../v1beta2/hostanalyzer_shared.go | 1 + .../v1beta2/zz_generated.deepcopy.go | 5 ++ schemas/analyzer-troubleshoot-v1beta2.json | 76 +++++++++++++++++++ .../supportbundle-troubleshoot-v1beta2.json | 76 +++++++++++++++++++ 8 files changed, 354 insertions(+) diff --git a/config/crds/troubleshoot.sh_analyzers.yaml b/config/crds/troubleshoot.sh_analyzers.yaml index 5599be775..04842d45a 100644 --- a/config/crds/troubleshoot.sh_analyzers.yaml +++ b/config/crds/troubleshoot.sh_analyzers.yaml @@ -3156,6 +3156,55 @@ spec: required: - outcomes type: object + tls: + 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_hostcollectors.yaml b/config/crds/troubleshoot.sh_hostcollectors.yaml index 14d11a66a..2effcf852 100644 --- a/config/crds/troubleshoot.sh_hostcollectors.yaml +++ b/config/crds/troubleshoot.sh_hostcollectors.yaml @@ -1305,6 +1305,55 @@ spec: required: - outcomes type: object + tls: + 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_hostpreflights.yaml b/config/crds/troubleshoot.sh_hostpreflights.yaml index d4ccb5dac..a0a842451 100644 --- a/config/crds/troubleshoot.sh_hostpreflights.yaml +++ b/config/crds/troubleshoot.sh_hostpreflights.yaml @@ -1305,6 +1305,55 @@ spec: required: - outcomes type: object + tls: + 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_supportbundles.yaml b/config/crds/troubleshoot.sh_supportbundles.yaml index 2b7c6d0f2..02b7704bc 100644 --- a/config/crds/troubleshoot.sh_supportbundles.yaml +++ b/config/crds/troubleshoot.sh_supportbundles.yaml @@ -20247,6 +20247,55 @@ spec: required: - outcomes type: object + tls: + 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/pkg/apis/troubleshoot/v1beta2/hostanalyzer_shared.go b/pkg/apis/troubleshoot/v1beta2/hostanalyzer_shared.go index 03825515f..fc0462ed3 100644 --- a/pkg/apis/troubleshoot/v1beta2/hostanalyzer_shared.go +++ b/pkg/apis/troubleshoot/v1beta2/hostanalyzer_shared.go @@ -182,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"` + TLS *TLSAnalyze `json:"tls,omitempty" yaml:"tls,omitempty"` } diff --git a/pkg/apis/troubleshoot/v1beta2/zz_generated.deepcopy.go b/pkg/apis/troubleshoot/v1beta2/zz_generated.deepcopy.go index 73ee91855..1afd1592d 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.TLS != nil { + in, out := &in.TLS, &out.TLS + *out = new(TLSAnalyze) + (*in).DeepCopyInto(*out) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HostAnalyze. diff --git a/schemas/analyzer-troubleshoot-v1beta2.json b/schemas/analyzer-troubleshoot-v1beta2.json index 13504a473..65e7f5246 100644 --- a/schemas/analyzer-troubleshoot-v1beta2.json +++ b/schemas/analyzer-troubleshoot-v1beta2.json @@ -4808,6 +4808,82 @@ } } }, + "tls": { + "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/supportbundle-troubleshoot-v1beta2.json b/schemas/supportbundle-troubleshoot-v1beta2.json index 3a4e485fc..f09fc370d 100644 --- a/schemas/supportbundle-troubleshoot-v1beta2.json +++ b/schemas/supportbundle-troubleshoot-v1beta2.json @@ -19399,6 +19399,82 @@ } } }, + "tls": { + "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": [ From d10a121d190118dd2770782926e2a75f0d5afdca Mon Sep 17 00:00:00 2001 From: Andrew Lavery Date: Tue, 1 Apr 2025 12:18:20 -0500 Subject: [PATCH 11/20] collector name is required in analyzer --- pkg/analyze/host_tls.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/analyze/host_tls.go b/pkg/analyze/host_tls.go index 1fe95a77e..fcf306ab1 100644 --- a/pkg/analyze/host_tls.go +++ b/pkg/analyze/host_tls.go @@ -28,7 +28,7 @@ func (a *AnalyzeHostTLS) Analyze( collectorName := a.hostAnalyzer.CollectorName if collectorName == "" { - collectorName = "result" + return nil, fmt.Errorf("collector name is required") } const nodeBaseDir = "host-collectors/tls" From d6bce8526cac5238597e1a107b140ce8395c90f3 Mon Sep 17 00:00:00 2001 From: Andrew Lavery Date: Tue, 1 Apr 2025 12:27:25 -0500 Subject: [PATCH 12/20] fix unit tests --- pkg/analyze/host_tls_test.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pkg/analyze/host_tls_test.go b/pkg/analyze/host_tls_test.go index bdb43d020..6d64c5bf8 100644 --- a/pkg/analyze/host_tls_test.go +++ b/pkg/analyze/host_tls_test.go @@ -28,6 +28,7 @@ func TestAnalyzeHostTLS(t *testing.T) { }, }, hostAnalyzer: &troubleshootv1beta2.TLSAnalyze{ + CollectorName: "test-tls", Outcomes: []*troubleshootv1beta2.Outcome{ { Fail: &troubleshootv1beta2.SingleOutcome{ @@ -68,6 +69,7 @@ func TestAnalyzeHostTLS(t *testing.T) { }, }, hostAnalyzer: &troubleshootv1beta2.TLSAnalyze{ + CollectorName: "test-tls", Outcomes: []*troubleshootv1beta2.Outcome{ { Fail: &troubleshootv1beta2.SingleOutcome{ @@ -88,6 +90,7 @@ func TestAnalyzeHostTLS(t *testing.T) { }, }, hostAnalyzer: &troubleshootv1beta2.TLSAnalyze{ + CollectorName: "test-tls", Outcomes: []*troubleshootv1beta2.Outcome{ { Pass: &troubleshootv1beta2.SingleOutcome{ @@ -124,6 +127,7 @@ func TestAnalyzeHostTLS(t *testing.T) { }, }, hostAnalyzer: &troubleshootv1beta2.TLSAnalyze{ + CollectorName: "test-tls", Outcomes: []*troubleshootv1beta2.Outcome{ { Pass: &troubleshootv1beta2.SingleOutcome{ @@ -171,6 +175,7 @@ func TestAnalyzeHostTLS(t *testing.T) { }, }, hostAnalyzer: &troubleshootv1beta2.TLSAnalyze{ + CollectorName: "test-tls", Outcomes: []*troubleshootv1beta2.Outcome{ { Pass: &troubleshootv1beta2.SingleOutcome{ From 09ff342ecad9bb300085ef38f3d6a6b56bbc4d55 Mon Sep 17 00:00:00 2001 From: Andrew Lavery Date: Tue, 1 Apr 2025 12:31:11 -0500 Subject: [PATCH 13/20] add this to the e2e tests --- test/e2e/preflight/spec/localHostCollectors.yaml | 11 +++++++++++ test/e2e/support-bundle/spec/localHostCollectors.yaml | 3 +++ 2 files changed, 14 insertions(+) diff --git a/test/e2e/preflight/spec/localHostCollectors.yaml b/test/e2e/preflight/spec/localHostCollectors.yaml index 701a054b9..fa1a2699b 100644 --- a/test/e2e/preflight/spec/localHostCollectors.yaml +++ b/test/e2e/preflight/spec/localHostCollectors.yaml @@ -5,6 +5,9 @@ metadata: spec: collectors: - cpu: {} + - tls: + address: "replicated.app:443" + collectorName: tls analyzers: - cpu: checkName: "Number of CPUs" @@ -17,3 +20,11 @@ spec: message: At least 4 CPU cores are recommended - pass: message: This server has at least 4 CPU cores + - tls: + 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 From 4ee38f3de554ca9a99097bb89195635d3b7a7032 Mon Sep 17 00:00:00 2001 From: Andrew Lavery Date: Tue, 1 Apr 2025 13:02:48 -0500 Subject: [PATCH 14/20] fix getting host analyzer --- pkg/analyze/host_analyzer.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/analyze/host_analyzer.go b/pkg/analyze/host_analyzer.go index fb450ff76..d60f40d96 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.TLS != nil: + return &AnalyzeHostTLS{analyzer.TLS}, true default: return nil, false } From d3b915f58a58f9b4792b961af62d533e9706a66b Mon Sep 17 00:00:00 2001 From: Andrew Lavery Date: Tue, 1 Apr 2025 13:54:10 -0500 Subject: [PATCH 15/20] fix collection --- pkg/analyze/host_tls.go | 1 - pkg/collect/host_collector.go | 2 ++ pkg/collect/host_tls.go | 9 ++++++--- test/e2e/preflight/spec/localHostCollectors.yaml | 1 + 4 files changed, 9 insertions(+), 4 deletions(-) diff --git a/pkg/analyze/host_tls.go b/pkg/analyze/host_tls.go index fcf306ab1..cf9bfa451 100644 --- a/pkg/analyze/host_tls.go +++ b/pkg/analyze/host_tls.go @@ -54,7 +54,6 @@ func (a *AnalyzeHostTLS) Analyze( } func (a *AnalyzeHostTLS) 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) diff --git a/pkg/collect/host_collector.go b/pkg/collect/host_collector.go index 855c06ecd..c345c7545 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.HostTLS != nil: + return &CollectHostTLS{collector.HostTLS, bundlePath}, true default: return nil, false } diff --git a/pkg/collect/host_tls.go b/pkg/collect/host_tls.go index 4f9ede3e7..3851b5859 100644 --- a/pkg/collect/host_tls.go +++ b/pkg/collect/host_tls.go @@ -60,15 +60,18 @@ func (c *CollectHostTLS) Collect(progressChan chan<- interface{}) (map[string][] return nil, errors.Wrap(err, "failed to marshal tls info") } - output := NewResult() - output.SaveResult(c.BundlePath, HostTimePath, bytes.NewBuffer(b)) - collectorName := c.hostCollector.CollectorName if collectorName == "" { collectorName = strings.ReplaceAll(c.hostCollector.Address, ":", "-") } name := filepath.Join("host-collectors/tls", 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 diff --git a/test/e2e/preflight/spec/localHostCollectors.yaml b/test/e2e/preflight/spec/localHostCollectors.yaml index fa1a2699b..a6b7021cc 100644 --- a/test/e2e/preflight/spec/localHostCollectors.yaml +++ b/test/e2e/preflight/spec/localHostCollectors.yaml @@ -21,6 +21,7 @@ spec: - pass: message: This server has at least 4 CPU cores - tls: + checkName: "replicated.app TLS certificate" collectorName: tls outcomes: - pass: From e139fb54a1d3a6cb5b5bc4b782f205c25b69a2bb Mon Sep 17 00:00:00 2001 From: Andrew Lavery Date: Tue, 1 Apr 2025 15:23:01 -0500 Subject: [PATCH 16/20] use dorequest to collect tls info --- pkg/collect/host_tls.go | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/pkg/collect/host_tls.go b/pkg/collect/host_tls.go index 3851b5859..c143eec87 100644 --- a/pkg/collect/host_tls.go +++ b/pkg/collect/host_tls.go @@ -2,8 +2,8 @@ package collect import ( "bytes" - "crypto/tls" "encoding/json" + "fmt" "path/filepath" "strings" "time" @@ -29,16 +29,12 @@ func (c *CollectHostTLS) IsExcluded() (bool, error) { func (c *CollectHostTLS) Collect(progressChan chan<- interface{}) (map[string][]byte, error) { tlsInfo := types.TLSInfo{} - conf := &tls.Config{ - InsecureSkipVerify: true, - } - - conn, err := tls.Dial("tcp", c.hostCollector.Address, conf) + resp, err := doRequest("GET", fmt.Sprintf("https://%s", c.hostCollector.Address), nil, "", true, "", nil, "") if err != nil { tlsInfo.Error = err.Error() } else { - defer conn.Close() - certs := conn.ConnectionState().PeerCertificates + defer resp.Body.Close() + certs := resp.TLS.PeerCertificates cleanedCerts := make([]types.CertInfo, len(certs)) for i, cert := range certs { cleanedCerts[i] = types.CertInfo{ From 727d82e4e9466ae9892eb02edc696fe130e312be Mon Sep 17 00:00:00 2001 From: Andrew Lavery Date: Tue, 1 Apr 2025 15:29:21 -0500 Subject: [PATCH 17/20] support collecting certificate from behind a proxy --- config/crds/troubleshoot.sh_collectors.yaml | 2 ++ config/crds/troubleshoot.sh_hostcollectors.yaml | 2 ++ config/crds/troubleshoot.sh_hostpreflights.yaml | 2 ++ config/crds/troubleshoot.sh_supportbundles.yaml | 2 ++ pkg/apis/troubleshoot/v1beta2/hostcollector_shared.go | 1 + pkg/collect/host_tls.go | 2 +- schemas/collector-troubleshoot-v1beta2.json | 3 +++ schemas/supportbundle-troubleshoot-v1beta2.json | 3 +++ 8 files changed, 16 insertions(+), 1 deletion(-) diff --git a/config/crds/troubleshoot.sh_collectors.yaml b/config/crds/troubleshoot.sh_collectors.yaml index fe84c99f1..1d4a3b198 100644 --- a/config/crds/troubleshoot.sh_collectors.yaml +++ b/config/crds/troubleshoot.sh_collectors.yaml @@ -17752,6 +17752,8 @@ spec: type: string exclude: type: BoolString + httpsProxy: + type: string required: - address type: object diff --git a/config/crds/troubleshoot.sh_hostcollectors.yaml b/config/crds/troubleshoot.sh_hostcollectors.yaml index 2effcf852..4bc19eb01 100644 --- a/config/crds/troubleshoot.sh_hostcollectors.yaml +++ b/config/crds/troubleshoot.sh_hostcollectors.yaml @@ -2027,6 +2027,8 @@ spec: type: string exclude: type: BoolString + httpsProxy: + type: string required: - address type: object diff --git a/config/crds/troubleshoot.sh_hostpreflights.yaml b/config/crds/troubleshoot.sh_hostpreflights.yaml index a0a842451..3ad473dfd 100644 --- a/config/crds/troubleshoot.sh_hostpreflights.yaml +++ b/config/crds/troubleshoot.sh_hostpreflights.yaml @@ -2027,6 +2027,8 @@ spec: type: string exclude: type: BoolString + httpsProxy: + type: string required: - address type: object diff --git a/config/crds/troubleshoot.sh_supportbundles.yaml b/config/crds/troubleshoot.sh_supportbundles.yaml index 02b7704bc..8d3f19cbb 100644 --- a/config/crds/troubleshoot.sh_supportbundles.yaml +++ b/config/crds/troubleshoot.sh_supportbundles.yaml @@ -20969,6 +20969,8 @@ spec: type: string exclude: type: BoolString + httpsProxy: + type: string required: - address type: object diff --git a/pkg/apis/troubleshoot/v1beta2/hostcollector_shared.go b/pkg/apis/troubleshoot/v1beta2/hostcollector_shared.go index fd9b311db..f670ae38f 100644 --- a/pkg/apis/troubleshoot/v1beta2/hostcollector_shared.go +++ b/pkg/apis/troubleshoot/v1beta2/hostcollector_shared.go @@ -238,6 +238,7 @@ type HostSysctl struct { type HostTLS struct { HostCollectorMeta `json:",inline" yaml:",inline"` Address string `json:"address"` + HttpsProxy string `json:"httpsProxy,omitempty"` } type HostCollect struct { diff --git a/pkg/collect/host_tls.go b/pkg/collect/host_tls.go index c143eec87..390afb54a 100644 --- a/pkg/collect/host_tls.go +++ b/pkg/collect/host_tls.go @@ -29,7 +29,7 @@ func (c *CollectHostTLS) IsExcluded() (bool, error) { func (c *CollectHostTLS) Collect(progressChan chan<- interface{}) (map[string][]byte, error) { tlsInfo := types.TLSInfo{} - resp, err := doRequest("GET", fmt.Sprintf("https://%s", c.hostCollector.Address), nil, "", true, "", nil, "") + resp, err := doRequest("GET", fmt.Sprintf("https://%s", c.hostCollector.Address), nil, "", true, "", nil, c.hostCollector.HttpsProxy) if err != nil { tlsInfo.Error = err.Error() } else { diff --git a/schemas/collector-troubleshoot-v1beta2.json b/schemas/collector-troubleshoot-v1beta2.json index ce8c68f47..f8ac0f0e1 100644 --- a/schemas/collector-troubleshoot-v1beta2.json +++ b/schemas/collector-troubleshoot-v1beta2.json @@ -15497,6 +15497,9 @@ }, "exclude": { "oneOf": [{"type": "string"},{"type": "boolean"}] + }, + "httpsProxy": { + "type": "string" } } }, diff --git a/schemas/supportbundle-troubleshoot-v1beta2.json b/schemas/supportbundle-troubleshoot-v1beta2.json index f09fc370d..94ccb92f8 100644 --- a/schemas/supportbundle-troubleshoot-v1beta2.json +++ b/schemas/supportbundle-troubleshoot-v1beta2.json @@ -20443,6 +20443,9 @@ }, "exclude": { "oneOf": [{"type": "string"},{"type": "boolean"}] + }, + "httpsProxy": { + "type": "string" } } }, From b2c10c78073fa868dbfb0e789b583a2c8e5db1e4 Mon Sep 17 00:00:00 2001 From: Andrew Lavery Date: Tue, 1 Apr 2025 16:01:22 -0500 Subject: [PATCH 18/20] rename to tlsCertificate --- cmd/collect/cli/root.go | 2 +- cmd/troubleshoot/cli/root.go | 2 +- pkg/analyze/host_analyzer.go | 4 ++-- .../{host_tls.go => host_tls_certificate.go} | 14 +++++++------- ...ls_test.go => host_tls_certificate_test.go} | 10 +++++----- .../v1beta2/hostanalyzer_shared.go | 2 +- .../v1beta2/hostcollector_shared.go | 4 ++-- .../v1beta2/zz_generated.deepcopy.go | 18 +++++++++--------- pkg/collect/host_collector.go | 4 ++-- .../{host_tls.go => host_tls_certificate.go} | 14 +++++++------- ...ls_test.go => host_tls_certificate_test.go} | 16 ++++++++-------- pkg/collect/http_test.go | 2 +- pkg/collect/postgres.go | 10 +++++----- pkg/collect/redis.go | 2 +- pkg/collect/util_test.go | 4 ++-- .../preflight/spec/localHostCollectors.yaml | 4 ++-- 16 files changed, 56 insertions(+), 56 deletions(-) rename pkg/analyze/{host_tls.go => host_tls_certificate.go} (84%) rename pkg/analyze/{host_tls_test.go => host_tls_certificate_test.go} (97%) rename pkg/collect/{host_tls.go => host_tls_certificate.go} (76%) rename pkg/collect/{host_tls_test.go => host_tls_certificate_test.go} (92%) 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/pkg/analyze/host_analyzer.go b/pkg/analyze/host_analyzer.go index d60f40d96..1e2fa098c 100644 --- a/pkg/analyze/host_analyzer.go +++ b/pkg/analyze/host_analyzer.go @@ -67,8 +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.TLS != nil: - return &AnalyzeHostTLS{analyzer.TLS}, true + case analyzer.TLSCertificate != nil: + return &AnalyzeHostTLSCertificate{analyzer.TLSCertificate}, true default: return nil, false } diff --git a/pkg/analyze/host_tls.go b/pkg/analyze/host_tls_certificate.go similarity index 84% rename from pkg/analyze/host_tls.go rename to pkg/analyze/host_tls_certificate.go index cf9bfa451..e979072d7 100644 --- a/pkg/analyze/host_tls.go +++ b/pkg/analyze/host_tls_certificate.go @@ -10,19 +10,19 @@ import ( troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2" ) -type AnalyzeHostTLS struct { +type AnalyzeHostTLSCertificate struct { hostAnalyzer *troubleshootv1beta2.TLSAnalyze } -func (a *AnalyzeHostTLS) Title() string { - return hostAnalyzerTitleOrDefault(a.hostAnalyzer.AnalyzeMeta, "TLS") +func (a *AnalyzeHostTLSCertificate) Title() string { + return hostAnalyzerTitleOrDefault(a.hostAnalyzer.AnalyzeMeta, "TLSCertificate") } -func (a *AnalyzeHostTLS) IsExcluded() (bool, error) { +func (a *AnalyzeHostTLSCertificate) IsExcluded() (bool, error) { return isExcluded(a.hostAnalyzer.Exclude) } -func (a *AnalyzeHostTLS) Analyze( +func (a *AnalyzeHostTLSCertificate) Analyze( getCollectedFileContents func(string) ([]byte, error), findFiles getChildCollectedFileContents, ) ([]*AnalyzeResult, error) { @@ -31,7 +31,7 @@ func (a *AnalyzeHostTLS) Analyze( return nil, fmt.Errorf("collector name is required") } - const nodeBaseDir = "host-collectors/tls" + const nodeBaseDir = "host-collectors/tls-certificate" localPath := fmt.Sprintf("%s/%s.json", nodeBaseDir, collectorName) fileName := fmt.Sprintf("%s.json", collectorName) @@ -53,7 +53,7 @@ func (a *AnalyzeHostTLS) Analyze( return results, nil } -func (a *AnalyzeHostTLS) CheckCondition(when string, data []byte) (bool, error) { +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) diff --git a/pkg/analyze/host_tls_test.go b/pkg/analyze/host_tls_certificate_test.go similarity index 97% rename from pkg/analyze/host_tls_test.go rename to pkg/analyze/host_tls_certificate_test.go index 6d64c5bf8..841190523 100644 --- a/pkg/analyze/host_tls_test.go +++ b/pkg/analyze/host_tls_certificate_test.go @@ -52,7 +52,7 @@ func TestAnalyzeHostTLS(t *testing.T) { }, result: []*AnalyzeResult{ { - Title: "TLS", + Title: "TLSCertificate", IsPass: true, Message: "issuer was foo", }, @@ -108,7 +108,7 @@ func TestAnalyzeHostTLS(t *testing.T) { }, result: []*AnalyzeResult{ { - Title: "TLS", + Title: "TLSCertificate", IsFail: true, Message: "issuer was not abc", }, @@ -145,7 +145,7 @@ func TestAnalyzeHostTLS(t *testing.T) { }, result: []*AnalyzeResult{ { - Title: "TLS", + Title: "TLSCertificate", IsPass: true, Message: "issuer was bar", }, @@ -193,7 +193,7 @@ func TestAnalyzeHostTLS(t *testing.T) { }, result: []*AnalyzeResult{ { - Title: "TLS", + Title: "TLSCertificate", IsPass: true, Message: "The issuer for replicated.app is E5 (letsencrypt) as expected", }, @@ -212,7 +212,7 @@ func TestAnalyzeHostTLS(t *testing.T) { return b, nil } - result, err := (&AnalyzeHostTLS{test.hostAnalyzer}).Analyze(getCollectedFileContents, nil) + result, err := (&AnalyzeHostTLSCertificate{test.hostAnalyzer}).Analyze(getCollectedFileContents, nil) if test.expectErr { req.Error(err) } else { diff --git a/pkg/apis/troubleshoot/v1beta2/hostanalyzer_shared.go b/pkg/apis/troubleshoot/v1beta2/hostanalyzer_shared.go index fc0462ed3..332642191 100644 --- a/pkg/apis/troubleshoot/v1beta2/hostanalyzer_shared.go +++ b/pkg/apis/troubleshoot/v1beta2/hostanalyzer_shared.go @@ -182,5 +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"` - TLS *TLSAnalyze `json:"tls,omitempty" yaml:"tls,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 f670ae38f..a5a37d334 100644 --- a/pkg/apis/troubleshoot/v1beta2/hostcollector_shared.go +++ b/pkg/apis/troubleshoot/v1beta2/hostcollector_shared.go @@ -235,7 +235,7 @@ type HostSysctl struct { HostCollectorMeta `json:",inline" yaml:",inline"` } -type HostTLS struct { +type HostTLSCertificate struct { HostCollectorMeta `json:",inline" yaml:",inline"` Address string `json:"address"` HttpsProxy string `json:"httpsProxy,omitempty"` @@ -271,7 +271,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"` - HostTLS *HostTLS `json:"tls,omitempty" yaml:"tls,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 1afd1592d..f2b385809 100644 --- a/pkg/apis/troubleshoot/v1beta2/zz_generated.deepcopy.go +++ b/pkg/apis/troubleshoot/v1beta2/zz_generated.deepcopy.go @@ -1973,8 +1973,8 @@ func (in *HostAnalyze) DeepCopyInto(out *HostAnalyze) { *out = new(HostSysctlAnalyze) (*in).DeepCopyInto(*out) } - if in.TLS != nil { - in, out := &in.TLS, &out.TLS + if in.TLSCertificate != nil { + in, out := &in.TLSCertificate, &out.TLSCertificate *out = new(TLSAnalyze) (*in).DeepCopyInto(*out) } @@ -2218,9 +2218,9 @@ func (in *HostCollect) DeepCopyInto(out *HostCollect) { *out = new(HostSysctl) (*in).DeepCopyInto(*out) } - if in.HostTLS != nil { - in, out := &in.HostTLS, &out.HostTLS - *out = new(HostTLS) + if in.HostTLSCertificate != nil { + in, out := &in.HostTLSCertificate, &out.HostTLSCertificate + *out = new(HostTLSCertificate) (*in).DeepCopyInto(*out) } } @@ -2914,17 +2914,17 @@ func (in *HostSystemPackages) DeepCopy() *HostSystemPackages { } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *HostTLS) DeepCopyInto(out *HostTLS) { +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 HostTLS. -func (in *HostTLS) DeepCopy() *HostTLS { +// 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(HostTLS) + out := new(HostTLSCertificate) in.DeepCopyInto(out) return out } diff --git a/pkg/collect/host_collector.go b/pkg/collect/host_collector.go index c345c7545..e72ec29d9 100644 --- a/pkg/collect/host_collector.go +++ b/pkg/collect/host_collector.go @@ -105,8 +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.HostTLS != nil: - return &CollectHostTLS{collector.HostTLS, bundlePath}, true + case collector.HostTLSCertificate != nil: + return &CollectHostTLSCertificate{collector.HostTLSCertificate, bundlePath}, true default: return nil, false } diff --git a/pkg/collect/host_tls.go b/pkg/collect/host_tls_certificate.go similarity index 76% rename from pkg/collect/host_tls.go rename to pkg/collect/host_tls_certificate.go index 390afb54a..446e6a359 100644 --- a/pkg/collect/host_tls.go +++ b/pkg/collect/host_tls_certificate.go @@ -13,20 +13,20 @@ import ( troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2" ) -type CollectHostTLS struct { - hostCollector *troubleshootv1beta2.HostTLS +type CollectHostTLSCertificate struct { + hostCollector *troubleshootv1beta2.HostTLSCertificate BundlePath string } -func (c *CollectHostTLS) Title() string { +func (c *CollectHostTLSCertificate) Title() string { return hostCollectorTitleOrDefault(c.hostCollector.HostCollectorMeta, "TCP Port Status") } -func (c *CollectHostTLS) IsExcluded() (bool, error) { +func (c *CollectHostTLSCertificate) IsExcluded() (bool, error) { return isExcluded(c.hostCollector.Exclude) } -func (c *CollectHostTLS) Collect(progressChan chan<- interface{}) (map[string][]byte, error) { +func (c *CollectHostTLSCertificate) Collect(progressChan chan<- interface{}) (map[string][]byte, error) { tlsInfo := types.TLSInfo{} resp, err := doRequest("GET", fmt.Sprintf("https://%s", c.hostCollector.Address), nil, "", true, "", nil, c.hostCollector.HttpsProxy) @@ -60,7 +60,7 @@ func (c *CollectHostTLS) Collect(progressChan chan<- interface{}) (map[string][] if collectorName == "" { collectorName = strings.ReplaceAll(c.hostCollector.Address, ":", "-") } - name := filepath.Join("host-collectors/tls", collectorName+".json") + name := filepath.Join("host-collectors/tls-certificate", collectorName+".json") output := NewResult() err = output.SaveResult(c.BundlePath, name, bytes.NewBuffer(b)) @@ -73,6 +73,6 @@ func (c *CollectHostTLS) Collect(progressChan chan<- interface{}) (map[string][] }, nil } -func (c *CollectHostTLS) RemoteCollect(progressChan chan<- interface{}) (map[string][]byte, error) { +func (c *CollectHostTLSCertificate) RemoteCollect(progressChan chan<- interface{}) (map[string][]byte, error) { return nil, ErrRemoteCollectorNotImplemented } diff --git a/pkg/collect/host_tls_test.go b/pkg/collect/host_tls_certificate_test.go similarity index 92% rename from pkg/collect/host_tls_test.go rename to pkg/collect/host_tls_certificate_test.go index 3c4302b4a..89ce40c46 100644 --- a/pkg/collect/host_tls_test.go +++ b/pkg/collect/host_tls_certificate_test.go @@ -29,7 +29,7 @@ func TestCollectHostTLS_Collect(t *testing.T) { cert, key, err := generateTestSelfSignedCert() require.NoError(t, err) - // Start a test TLS server + // Start a test TLSCertificate server serverAddr, closeServer, err := startTestHttpsServer(cert, key) require.NoError(t, err) defer closeServer() @@ -52,13 +52,13 @@ func TestCollectHostTLS_Collect(t *testing.T) { tests := []struct { name string - hostCollector *troubleshootv1beta2.HostTLS + hostCollector *troubleshootv1beta2.HostTLSCertificate certFields []certFields wantErr bool }{ { name: "successful collection", - hostCollector: &troubleshootv1beta2.HostTLS{ + hostCollector: &troubleshootv1beta2.HostTLSCertificate{ Address: serverAddr, HostCollectorMeta: troubleshootv1beta2.HostCollectorMeta{ CollectorName: "test-tls", @@ -75,7 +75,7 @@ func TestCollectHostTLS_Collect(t *testing.T) { }, { name: "failed connection", - hostCollector: &troubleshootv1beta2.HostTLS{ + hostCollector: &troubleshootv1beta2.HostTLSCertificate{ Address: "invalid-address:9999", HostCollectorMeta: troubleshootv1beta2.HostCollectorMeta{ CollectorName: "test-tls-failed", @@ -87,7 +87,7 @@ func TestCollectHostTLS_Collect(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - c := &CollectHostTLS{ + c := &CollectHostTLSCertificate{ hostCollector: tt.hostCollector, BundlePath: bundlePath, } @@ -166,7 +166,7 @@ func generateTestSelfSignedCert() ([]byte, []byte, error) { return derBytes, privateKeyDER, nil } -// Helper function to start a test TLS server +// 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}) @@ -180,10 +180,10 @@ func startTestHttpsServer(certDER, keyDER []byte) (string, func(), error) { // Create a simple HTTP handler handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) - w.Write([]byte("TLS Test Server")) + w.Write([]byte("TLSCertificate Test Server")) }) - // Use httptest package to create a TLS server + // Use httptest package to create a TLSCertificate server testServer := httptest.NewUnstartedServer(handler) testServer.TLS = &tls.Config{ Certificates: []tls.Certificate{pair}, 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/test/e2e/preflight/spec/localHostCollectors.yaml b/test/e2e/preflight/spec/localHostCollectors.yaml index a6b7021cc..daf715d28 100644 --- a/test/e2e/preflight/spec/localHostCollectors.yaml +++ b/test/e2e/preflight/spec/localHostCollectors.yaml @@ -5,7 +5,7 @@ metadata: spec: collectors: - cpu: {} - - tls: + - tlsCertificate: address: "replicated.app:443" collectorName: tls analyzers: @@ -20,7 +20,7 @@ spec: message: At least 4 CPU cores are recommended - pass: message: This server has at least 4 CPU cores - - tls: + - tlsCertificate: checkName: "replicated.app TLS certificate" collectorName: tls outcomes: From 8eb674c3d81a1183e616d21181d66ef6409ec721 Mon Sep 17 00:00:00 2001 From: Andrew Lavery Date: Tue, 1 Apr 2025 16:36:19 -0500 Subject: [PATCH 19/20] allow fetching the expected set of certs from the remote server --- pkg/analyze/host_tls_certificate.go | 39 ++++-- pkg/analyze/host_tls_certificate_test.go | 124 ++++++++++++++++++ pkg/analyze/types/tls_types.go | 1 + .../v1beta2/hostcollector_shared.go | 7 +- pkg/collect/host_tls_certificate.go | 21 ++- pkg/collect/host_tls_certificate_test.go | 54 +++++++- 6 files changed, 224 insertions(+), 22 deletions(-) diff --git a/pkg/analyze/host_tls_certificate.go b/pkg/analyze/host_tls_certificate.go index e979072d7..cab96a8a3 100644 --- a/pkg/analyze/host_tls_certificate.go +++ b/pkg/analyze/host_tls_certificate.go @@ -63,23 +63,36 @@ func (a *AnalyzeHostTLSCertificate) CheckCondition(when string, data []byte) (bo // currently this supports only checks like "issuer == foo" func compareHostTLSResult(when string, tlsInfo *types.TLSInfo) (bool, error) { - parts := strings.SplitN(when, " ", 3) - if len(parts) < 3 { - return false, fmt.Errorf("invalid when clause: %s", when) - } + parts := strings.Split(when, " ") checkType := parts[0] - if checkType != "issuer" { - return false, fmt.Errorf("invalid check type: %s", checkType) - } + 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) + } - issuer := parts[2] + expected := strings.Join(parts[2:], " ") - for _, cert := range tlsInfo.PeerCertificates { - if cert.Issuer == issuer { - return true, nil + 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 false, nil + 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 index 841190523..072811398 100644 --- a/pkg/analyze/host_tls_certificate_test.go +++ b/pkg/analyze/host_tls_certificate_test.go @@ -199,6 +199,130 @@ func TestAnalyzeHostTLS(t *testing.T) { }, }, }, + + { + 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) { diff --git a/pkg/analyze/types/tls_types.go b/pkg/analyze/types/tls_types.go index 07a4fc2aa..ec3b6b79f 100644 --- a/pkg/analyze/types/tls_types.go +++ b/pkg/analyze/types/tls_types.go @@ -12,5 +12,6 @@ type CertInfo struct { type TLSInfo struct { PeerCertificates []CertInfo `json:"peer_certificates"` + ExpectedCerts []CertInfo `json:"expected_certs"` Error string `json:"error"` } diff --git a/pkg/apis/troubleshoot/v1beta2/hostcollector_shared.go b/pkg/apis/troubleshoot/v1beta2/hostcollector_shared.go index a5a37d334..26cbf842d 100644 --- a/pkg/apis/troubleshoot/v1beta2/hostcollector_shared.go +++ b/pkg/apis/troubleshoot/v1beta2/hostcollector_shared.go @@ -236,9 +236,10 @@ type HostSysctl struct { } type HostTLSCertificate struct { - HostCollectorMeta `json:",inline" yaml:",inline"` - Address string `json:"address"` - HttpsProxy string `json:"httpsProxy,omitempty"` + HostCollectorMeta `json:",inline" yaml:",inline"` + Address string `json:"address"` + HttpsProxy string `json:"httpsProxy,omitempty"` + ExpectedCertSubpath string `json:"expectedCertSubpath,omitempty"` } type HostCollect struct { diff --git a/pkg/collect/host_tls_certificate.go b/pkg/collect/host_tls_certificate.go index 446e6a359..2514a87d5 100644 --- a/pkg/collect/host_tls_certificate.go +++ b/pkg/collect/host_tls_certificate.go @@ -4,6 +4,7 @@ import ( "bytes" "encoding/json" "fmt" + "io" "path/filepath" "strings" "time" @@ -29,7 +30,11 @@ func (c *CollectHostTLSCertificate) IsExcluded() (bool, error) { func (c *CollectHostTLSCertificate) Collect(progressChan chan<- interface{}) (map[string][]byte, error) { tlsInfo := types.TLSInfo{} - resp, err := doRequest("GET", fmt.Sprintf("https://%s", c.hostCollector.Address), nil, "", true, "", nil, c.hostCollector.HttpsProxy) + 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 { @@ -49,6 +54,20 @@ func (c *CollectHostTLSCertificate) Collect(progressChan chan<- interface{}) (ma } 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) diff --git a/pkg/collect/host_tls_certificate_test.go b/pkg/collect/host_tls_certificate_test.go index 89ce40c46..86048bfa3 100644 --- a/pkg/collect/host_tls_certificate_test.go +++ b/pkg/collect/host_tls_certificate_test.go @@ -8,6 +8,7 @@ import ( "crypto/x509/pkix" "encoding/json" "encoding/pem" + "fmt" "math/big" "net/http" "net/http/httptest" @@ -54,6 +55,7 @@ func TestCollectHostTLS_Collect(t *testing.T) { name string hostCollector *troubleshootv1beta2.HostTLSCertificate certFields []certFields + expectedCerts []certFields wantErr bool }{ { @@ -83,6 +85,31 @@ func TestCollectHostTLS_Collect(t *testing.T) { }, 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 { @@ -96,7 +123,7 @@ func TestCollectHostTLS_Collect(t *testing.T) { require.NoError(t, err) require.NotNil(t, collected) - expectedFilename := filepath.Join("host-collectors/tls", tt.hostCollector.CollectorName+".json") + expectedFilename := filepath.Join("host-collectors/tls-certificate", tt.hostCollector.CollectorName+".json") assert.Contains(t, collected, expectedFilename) // Validate the content @@ -109,16 +136,25 @@ func TestCollectHostTLS_Collect(t *testing.T) { 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, cert := range tlsInfo.PeerCertificates { - assert.Equal(t, tt.certFields[i].Issuer, cert.Issuer) - assert.Equal(t, tt.certFields[i].Subject, cert.Subject) - assert.Equal(t, tt.certFields[i].IsCA, cert.IsCA) + 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) } }) } @@ -179,6 +215,14 @@ func startTestHttpsServer(certDER, keyDER []byte) (string, func(), error) { // 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")) }) From 6539de660ec1931cda297bb831871c574f3c9f23 Mon Sep 17 00:00:00 2001 From: Andrew Lavery Date: Tue, 1 Apr 2025 16:45:01 -0500 Subject: [PATCH 20/20] make generate schemas --- config/crds/troubleshoot.sh_analyzers.yaml | 2 +- config/crds/troubleshoot.sh_collectors.yaml | 4 +++- config/crds/troubleshoot.sh_hostcollectors.yaml | 6 ++++-- config/crds/troubleshoot.sh_hostpreflights.yaml | 6 ++++-- config/crds/troubleshoot.sh_supportbundles.yaml | 6 ++++-- schemas/analyzer-troubleshoot-v1beta2.json | 2 +- schemas/collector-troubleshoot-v1beta2.json | 5 ++++- schemas/supportbundle-troubleshoot-v1beta2.json | 7 +++++-- 8 files changed, 26 insertions(+), 12 deletions(-) diff --git a/config/crds/troubleshoot.sh_analyzers.yaml b/config/crds/troubleshoot.sh_analyzers.yaml index 04842d45a..235dd6f65 100644 --- a/config/crds/troubleshoot.sh_analyzers.yaml +++ b/config/crds/troubleshoot.sh_analyzers.yaml @@ -3156,7 +3156,7 @@ spec: required: - outcomes type: object - tls: + tlsCertificate: properties: annotations: additionalProperties: diff --git a/config/crds/troubleshoot.sh_collectors.yaml b/config/crds/troubleshoot.sh_collectors.yaml index 1d4a3b198..56c3b80da 100644 --- a/config/crds/troubleshoot.sh_collectors.yaml +++ b/config/crds/troubleshoot.sh_collectors.yaml @@ -17744,7 +17744,7 @@ spec: exclude: type: BoolString type: object - tls: + tlsCertificate: properties: address: type: string @@ -17752,6 +17752,8 @@ spec: type: string exclude: type: BoolString + expectedCertSubpath: + type: string httpsProxy: type: string required: diff --git a/config/crds/troubleshoot.sh_hostcollectors.yaml b/config/crds/troubleshoot.sh_hostcollectors.yaml index 4bc19eb01..256aaa793 100644 --- a/config/crds/troubleshoot.sh_hostcollectors.yaml +++ b/config/crds/troubleshoot.sh_hostcollectors.yaml @@ -1305,7 +1305,7 @@ spec: required: - outcomes type: object - tls: + tlsCertificate: properties: annotations: additionalProperties: @@ -2019,7 +2019,7 @@ spec: exclude: type: BoolString type: object - tls: + tlsCertificate: properties: address: type: string @@ -2027,6 +2027,8 @@ spec: type: string exclude: type: BoolString + expectedCertSubpath: + type: string httpsProxy: type: string required: diff --git a/config/crds/troubleshoot.sh_hostpreflights.yaml b/config/crds/troubleshoot.sh_hostpreflights.yaml index 3ad473dfd..b53abd982 100644 --- a/config/crds/troubleshoot.sh_hostpreflights.yaml +++ b/config/crds/troubleshoot.sh_hostpreflights.yaml @@ -1305,7 +1305,7 @@ spec: required: - outcomes type: object - tls: + tlsCertificate: properties: annotations: additionalProperties: @@ -2019,7 +2019,7 @@ spec: exclude: type: BoolString type: object - tls: + tlsCertificate: properties: address: type: string @@ -2027,6 +2027,8 @@ spec: type: string exclude: type: BoolString + expectedCertSubpath: + type: string httpsProxy: type: string required: diff --git a/config/crds/troubleshoot.sh_supportbundles.yaml b/config/crds/troubleshoot.sh_supportbundles.yaml index 8d3f19cbb..84ba3623a 100644 --- a/config/crds/troubleshoot.sh_supportbundles.yaml +++ b/config/crds/troubleshoot.sh_supportbundles.yaml @@ -20247,7 +20247,7 @@ spec: required: - outcomes type: object - tls: + tlsCertificate: properties: annotations: additionalProperties: @@ -20961,7 +20961,7 @@ spec: exclude: type: BoolString type: object - tls: + tlsCertificate: properties: address: type: string @@ -20969,6 +20969,8 @@ spec: type: string exclude: type: BoolString + expectedCertSubpath: + type: string httpsProxy: type: string required: diff --git a/schemas/analyzer-troubleshoot-v1beta2.json b/schemas/analyzer-troubleshoot-v1beta2.json index 65e7f5246..e7cb96a3a 100644 --- a/schemas/analyzer-troubleshoot-v1beta2.json +++ b/schemas/analyzer-troubleshoot-v1beta2.json @@ -4808,7 +4808,7 @@ } } }, - "tls": { + "tlsCertificate": { "type": "object", "required": [ "outcomes" diff --git a/schemas/collector-troubleshoot-v1beta2.json b/schemas/collector-troubleshoot-v1beta2.json index f8ac0f0e1..8e103c29c 100644 --- a/schemas/collector-troubleshoot-v1beta2.json +++ b/schemas/collector-troubleshoot-v1beta2.json @@ -15483,7 +15483,7 @@ } } }, - "tls": { + "tlsCertificate": { "type": "object", "required": [ "address" @@ -15498,6 +15498,9 @@ "exclude": { "oneOf": [{"type": "string"},{"type": "boolean"}] }, + "expectedCertSubpath": { + "type": "string" + }, "httpsProxy": { "type": "string" } diff --git a/schemas/supportbundle-troubleshoot-v1beta2.json b/schemas/supportbundle-troubleshoot-v1beta2.json index 94ccb92f8..840c1df86 100644 --- a/schemas/supportbundle-troubleshoot-v1beta2.json +++ b/schemas/supportbundle-troubleshoot-v1beta2.json @@ -19399,7 +19399,7 @@ } } }, - "tls": { + "tlsCertificate": { "type": "object", "required": [ "outcomes" @@ -20429,7 +20429,7 @@ } } }, - "tls": { + "tlsCertificate": { "type": "object", "required": [ "address" @@ -20444,6 +20444,9 @@ "exclude": { "oneOf": [{"type": "string"},{"type": "boolean"}] }, + "expectedCertSubpath": { + "type": "string" + }, "httpsProxy": { "type": "string" }