tls.go 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
  1. // Copyright (c) Tailscale Inc & AUTHORS
  2. // SPDX-License-Identifier: BSD-3-Clause
  3. package prober
  4. import (
  5. "bytes"
  6. "context"
  7. "crypto/tls"
  8. "crypto/x509"
  9. "fmt"
  10. "io"
  11. "net"
  12. "net/http"
  13. "time"
  14. "github.com/pkg/errors"
  15. "golang.org/x/crypto/ocsp"
  16. "tailscale.com/util/multierr"
  17. )
  18. const expiresSoon = 7 * 24 * time.Hour // 7 days from now
  19. // TLS returns a Probe that healthchecks a TLS endpoint.
  20. //
  21. // The ProbeFunc connects to a hostname (host:port string), does a TLS
  22. // handshake, verifies that the hostname matches the presented certificate,
  23. // checks certificate validity time and OCSP revocation status.
  24. func TLS(hostname string) ProbeFunc {
  25. return func(ctx context.Context) error {
  26. return probeTLS(ctx, hostname)
  27. }
  28. }
  29. func probeTLS(ctx context.Context, hostname string) error {
  30. host, _, err := net.SplitHostPort(hostname)
  31. if err != nil {
  32. return err
  33. }
  34. dialer := &tls.Dialer{Config: &tls.Config{ServerName: host}}
  35. conn, err := dialer.DialContext(ctx, "tcp", hostname)
  36. if err != nil {
  37. return fmt.Errorf("connecting to %q: %w", hostname, err)
  38. }
  39. defer conn.Close()
  40. tlsConnState := conn.(*tls.Conn).ConnectionState()
  41. return validateConnState(ctx, &tlsConnState)
  42. }
  43. // validateConnState verifies certificate validity time in all certificates
  44. // returned by the TLS server and checks OCSP revocation status for the
  45. // leaf cert.
  46. func validateConnState(ctx context.Context, cs *tls.ConnectionState) (returnerr error) {
  47. var errs []error
  48. defer func() {
  49. returnerr = multierr.New(errs...)
  50. }()
  51. latestAllowedExpiration := time.Now().Add(expiresSoon)
  52. var leafCert *x509.Certificate
  53. var issuerCert *x509.Certificate
  54. var leafAuthorityKeyID string
  55. // PeerCertificates will never be len == 0 on the client side
  56. for i, cert := range cs.PeerCertificates {
  57. if i == 0 {
  58. leafCert = cert
  59. leafAuthorityKeyID = string(cert.AuthorityKeyId)
  60. }
  61. if i > 0 {
  62. if leafAuthorityKeyID == string(cert.SubjectKeyId) {
  63. issuerCert = cert
  64. }
  65. }
  66. // Do not check certificate validity period for self-signed certs.
  67. // The practical reason is to avoid raising alerts for expiring
  68. // DERP metaCert certificates that are returned as part of regular
  69. // TLS handshake.
  70. if string(cert.SubjectKeyId) == string(cert.AuthorityKeyId) {
  71. continue
  72. }
  73. if time.Now().Before(cert.NotBefore) {
  74. errs = append(errs, fmt.Errorf("one of the certs has NotBefore in the future (%v): %v", cert.NotBefore, cert.Subject))
  75. }
  76. if latestAllowedExpiration.After(cert.NotAfter) {
  77. left := cert.NotAfter.Sub(time.Now())
  78. errs = append(errs, fmt.Errorf("one of the certs expires in %v: %v", left, cert.Subject))
  79. }
  80. }
  81. if len(leafCert.OCSPServer) == 0 {
  82. errs = append(errs, fmt.Errorf("no OCSP server presented in leaf cert for %v", leafCert.Subject))
  83. return
  84. }
  85. ocspResp, err := getOCSPResponse(ctx, leafCert.OCSPServer[0], leafCert, issuerCert)
  86. if err != nil {
  87. errs = append(errs, errors.Wrapf(err, "OCSP verification failed for %v", leafCert.Subject))
  88. return
  89. }
  90. if ocspResp.Status == ocsp.Unknown {
  91. errs = append(errs, fmt.Errorf("unknown OCSP verification status for %v", leafCert.Subject))
  92. }
  93. if ocspResp.Status == ocsp.Revoked {
  94. errs = append(errs, fmt.Errorf("cert for %v has been revoked on %v, reason: %v", leafCert.Subject, ocspResp.RevokedAt, ocspResp.RevocationReason))
  95. }
  96. return
  97. }
  98. func getOCSPResponse(ctx context.Context, ocspServer string, leafCert, issuerCert *x509.Certificate) (*ocsp.Response, error) {
  99. reqb, err := ocsp.CreateRequest(leafCert, issuerCert, nil)
  100. if err != nil {
  101. return nil, errors.Wrap(err, "could not create OCSP request")
  102. }
  103. hreq, err := http.NewRequestWithContext(ctx, "POST", ocspServer, bytes.NewReader(reqb))
  104. if err != nil {
  105. return nil, errors.Wrap(err, "could not create OCSP POST request")
  106. }
  107. hreq.Header.Add("Content-Type", "application/ocsp-request")
  108. hreq.Header.Add("Accept", "application/ocsp-response")
  109. hresp, err := http.DefaultClient.Do(hreq)
  110. if err != nil {
  111. return nil, errors.Wrap(err, "OCSP request failed")
  112. }
  113. defer hresp.Body.Close()
  114. if hresp.StatusCode != http.StatusOK {
  115. return nil, fmt.Errorf("ocsp: non-200 status code from OCSP server: %s", hresp.Status)
  116. }
  117. lr := io.LimitReader(hresp.Body, 10<<20) // 10MB
  118. ocspB, err := io.ReadAll(lr)
  119. if err != nil {
  120. return nil, err
  121. }
  122. return ocsp.ParseResponse(ocspB, issuerCert)
  123. }