Überprüfen Sie das Ablaufdatum des SSL-Zertifikats für mehrere Remote-Server


18

Mit diesem OpenSSL-Befehl kann ich das Ablaufdatum von SSL-Zertifikaten ermitteln:

openssl x509 -noout -in <filename> -enddate

Aber wenn die Zertifikate auf verschiedenen Webservern verteilt sind, wie finden Sie das Ablaufdatum aller dieser Zertifikate auf allen Servern?

Es scheint eine Möglichkeit zu geben, eine Verbindung zu einem anderen Host herzustellen, ich bin mir jedoch nicht sicher, wie ich das Ablaufdatum mithilfe dieser Methode ermitteln kann:

openssl s_client -connect host:port

Antworten:


15

Ich hatte die gleiche Ausgabe und schrieb diese ... Es ist schnell und schmutzig, sollte aber funktionieren. Alle Zertifikate, die noch nicht gültig sind oder in den nächsten 90 Tagen verfallen, werden protokolliert (und beim Debuggen auf dem Bildschirm gedruckt). Könnte einige Bugs enthalten, kann aber jederzeit aufgeräumt werden.

#!/bin/sh

DEBUG=false
warning_days=90 # Number of days to warn about soon-to-expire certs
certs_to_check='serverA.test.co.uk:443
serverB.test.co.uk:8140
serverC.test.co.uk:443'

for CERT in $certs_to_check
do
  $DEBUG && echo "Checking cert: [$CERT]"

  output=$(echo | openssl s_client -connect ${CERT} 2>/dev/null |\
  sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' |\
  openssl x509 -noout -subject -dates 2>/dev/null) 

  if [ "$?" -ne 0 ]; then
    $DEBUG && echo "Error connecting to host for cert [$CERT]"
    logger -p local6.warn "Error connecting to host for cert [$CERT]"
    continue
  fi

  start_date=$(echo $output | sed 's/.*notBefore=\(.*\).*not.*/\1/g')
  end_date=$(echo $output | sed 's/.*notAfter=\(.*\)$/\1/g')

  start_epoch=$(date +%s -d "$start_date")
  end_epoch=$(date +%s -d "$end_date")

  epoch_now=$(date +%s)

  if [ "$start_epoch" -gt "$epoch_now" ]; then
    $DEBUG && echo "Certificate for [$CERT] is not yet valid"
    logger -p local6.warn "Certificate for $CERT is not yet valid"
  fi

  seconds_to_expire=$(($end_epoch - $epoch_now))
  days_to_expire=$(($seconds_to_expire / 86400))

  $DEBUG && echo "Days to expiry: ($days_to_expire)"

  warning_seconds=$((86400 * $warning_days))

  if [ "$seconds_to_expire" -lt "$warning_seconds" ]; then
    $DEBUG && echo "Cert [$CERT] is soon to expire ($seconds_to_expire seconds)"
    logger -p local6.warn "cert [$CERT] is soon to expire ($seconds_to_expire seconds)"
  fi
done

Bei Verwendung unter OS X wird der dateBefehl möglicherweise nicht ordnungsgemäß ausgeführt. Dies liegt an den Unterschieden in der Unix- und Linux-Version dieses Dienstprogramms. Der verknüpfte Beitrag verfügt über Optionen, mit denen diese Funktion ausgeführt werden kann.


Ich habe Ihr Skript ein wenig modifiziert / erweitert, um auch Mailserver-Zertifikate überprüfen zu können und einen kleinen Überblick über den Status aller Zertifikate zu geben. Sie finden das geänderte Skript unter: gist.github.com/lkiesow/c9c5d96ecb71822b82cd9d194c581cc8
Lars Kiesow

1
Wenn der Server SNI verwendet, müssen Sie die einschließen -servernameArgument, wie folgt aus :openssl s_client -servername example.com -connect example.com:443
Flimm

11

Führen Sie einfach den folgenden Befehl aus, und das Ablaufdatum wird angezeigt:

echo q | openssl s_client -connect google.com.br:443 | openssl x509 -noout -enddate

Mit diesem Befehl können Sie eine Batchdatei erstellen, um diese Informationen für mehrere Remoteserver zu erfassen.


2
So wie du das geschrieben hast, musst du STRG-C drücken, um es zu beenden. Sie könnten das beheben mit: openssl s_client -connect google.com.br:443 </ dev / null 2> & 1 | openssl x509 -noout -enddate Nur ein Gedanke.
Numberwhun

1
Wenn der Server SNI verwendet, müssen Sie das folgende -servernameArgument verwenden:openssl s_client -servername google.com.br -connect google.com.br:443
Flimm

6

Unten ist mein Skript, das zur Kontrolle in Nagios. Es stellt eine Verbindung zu einem bestimmten Host her und überprüft, ob das Zertifikat innerhalb eines mit den Optionen -c / -w festgelegten Schwellenwerts gültig ist. Es kann überprüfen, ob der CN des Zertifikats mit dem von Ihnen erwarteten Namen übereinstimmt.

Sie benötigen die Python-OpenSSL-Bibliothek, und ich habe alle Tests mit Python 2.7 durchgeführt.

Es wäre trivial, wenn ein Shell-Skript dies mehrmals aufruft. Das Skript gibt die Standard-Nagios-Exit-Werte für den Status Kritisch / Warnung / OK zurück.

So kann eine einfache Überprüfung des Google-Zertifikats durchgeführt werden.

./check_ssl_certificate -H www.google.com -p 443 -n www.google.com

Expire OK[108d] - CN OK - cn:www.google.com

check_ssl_certificate

#!/usr/bin/python

"""
Usage: check_ssl_certificate -H <host> -p <port> [-m <method>] 
                      [-c <days>] [-w <days>]
  -h show the help
  -H <HOST>    host/ip to check
  -p <port>    port number
  -m <method>  (SSLv2|SSLv3|SSLv23|TLSv1) defaults to SSLv23
  -c <days>    day threshold for critical
  -w <days>    day threshold for warning
  -n name      Check CN value is valid
"""

import getopt,sys
import __main__
from OpenSSL import SSL
import socket
import datetime

# On debian Based systems requires python-openssl

def get_options():
  "get options"

  options={'host':'',
           'port':'',
           'method':'SSLv23',
           'critical':5,
           'warning':15,
           'cn':''}

  try:
    opts, args = getopt.getopt(sys.argv[1:], "hH:p:m:c:w:n:", ['help', "host", 'port', 'method'])
  except getopt.GetoptError as err:
    # print help information and exit:
    print str(err) # will print something like "option -a not recognized"
    usage()
    sys.exit(2)
  for o, a in opts:
    if o in ("-h", "--help"):
      print __main__.__doc__
      sys.exit()
    elif o in ("-H", "--host"):
      options['host'] = a
      pass
    elif o in ("-p", "--port"):
      options['port'] = a
    elif o in ("-m", "--method"):
      options['method'] = a
    elif o == '-c':
      options['critical'] = int(a)
    elif o == '-w':
      options['warning'] = int(a)
    elif o == '-n':
      options['cn'] = a
    else:
      assert False, "unhandled option"

  if (''==options['host'] or 
      ''==options['port']):
    print __main__.__doc__
    sys.exit()

  if options['critical'] >= options['warning']:
    print "Critical must be smaller then warning"
    print __main__.__doc__
    sys.exit()

  return options

def main():
  options = get_options()

  # Initialize context
  if options['method']=='SSLv3':
    ctx = SSL.Context(SSL.SSLv3_METHOD)
  elif options['method']=='SSLv2':
    ctx = SSL.Context(SSL.SSLv2_METHOD)
  elif options['method']=='SSLv23':
    ctx = SSL.Context(SSL.SSLv23_METHOD)
  else:
    ctx = SSL.Context(SSL.TLSv1_METHOD)

  # Set up client
  sock = SSL.Connection(ctx, socket.socket(socket.AF_INET, socket.SOCK_STREAM))
  sock.connect((options['host'], int(options['port'])))
  # Send an EOF
  try:
    sock.send("\x04")
    sock.shutdown()
    peer_cert=sock.get_peer_certificate()
    sock.close()
  except SSL.Error,e:
    print e

  exit_status=0
  exit_message=[]

  cur_date = datetime.datetime.utcnow()
  cert_nbefore = datetime.datetime.strptime(peer_cert.get_notBefore(),'%Y%m%d%H%M%SZ')
  cert_nafter = datetime.datetime.strptime(peer_cert.get_notAfter(),'%Y%m%d%H%M%SZ')

  expire_days = int((cert_nafter - cur_date).days)

  if cert_nbefore > cur_date:
    if exit_status < 2: 
      exit_status = 2
    exit_message.append('C: cert is not valid')
  elif expire_days < 0:
    if exit_status < 2: 
      exit_status = 2
    exit_message.append('Expire critical (expired)')
  elif options['critical'] > expire_days:
    if exit_status < 2: 
      exit_status = 2
    exit_message.append('Expire critical')
  elif options['warning'] > expire_days:
    if exit_status < 1: 
      exit_status = 1
    exit_message.append('Expire warning')
  else:
    exit_message.append('Expire OK')

  exit_message.append('['+str(expire_days)+'d]')

  for part in peer_cert.get_subject().get_components():
    if part[0]=='CN':
      cert_cn=part[1]

  if options['cn']!='' and options['cn'].lower()!=cert_cn.lower():
    if exit_status < 2:
      exit_status = 2
    exit_message.append(' - CN mismatch')
  else:
    exit_message.append(' - CN OK')

  exit_message.append(' - cn:'+cert_cn)

  print ''.join(exit_message)
  sys.exit(exit_status)

if __name__ == "__main__":
  main()

2

get_pem

Stellen Sie eine Verbindung zu host: port her, extrahieren Sie das Zertifikat mit sed und schreiben Sie es in /tmp/host.port.pem.

get_expiration_date

Lesen Sie die angegebene pem-Datei und werten Sie den notAfter-Schlüssel als Bash-Variable aus. Drucken Sie dann den Dateinamen und das Datum, an dem er in einem bestimmten Gebietsschema abläuft.

get_pem_expiration_dates

Iterieren Sie eine Eingabedatei und führen Sie die obigen Funktionen aus.

check.pems.sh

#!/bin/bash
get_pem () {
    openssl s_client -connect $1:$2 < /dev/null |& \
    sed -n '/BEGIN CERTIFICATE/,/END CERTIFICATE/w'/tmp/$1.$2.pem
}
get_expiration_date () {
    local pemfile=$1 notAfter
    if [ -s $pemfile ]; then
        eval `
          openssl x509 -noout -enddate -in /tmp/$pemfile |
          sed -E 's/=(.*)/="\1"/'
        `
        printf "%40s: " $pemfile
        LC_ALL=ru_RU.utf-8 date -d "$notAfter" +%c
    else
        printf "%40s: %s\n" $pemfile '???'
    fi
}

get_pem_expiration_dates () {
    local pemfile server port
    while read host; do
        pemfile=${host/ /.}.pem
        server=${host% *}
        port=${host#* }
        if [ ! -f /tmp/$pemfile ]; then get_pem $server $port; fi
        if [   -f /tmp/$pemfile ]; then get_expiration_date $pemfile; fi
    done < ${1:-input.txt}
}

if [ -f "$1" ]; then
    get_pem_expiration_dates "$1" ; fi

Beispielausgabe

 $ sh check.pems.sh input.txt
             www.google.com.443.pem: Пн. 30 дек. 2013 01:00:00
              superuser.com.443.pem: Чт. 13 марта 2014 13:00:00
               slashdot.org.443.pem: Сб. 24 мая 2014 00:49:50
          movielens.umn.edu.443.pem: ???
 $ cat input.txt
 www.google.com 443
 superuser.com 443
 slashdot.org 443
 movielens.umn.edu 443

Und um Ihre Frage zu beantworten:

$ openssl s_client -connect www.google.com:443 </dev/null |& \
sed -n '/BEGIN CERTIFICATE/,/END CERTIFICATE/p' | \
openssl x509 -noout -enddate |& \
grep ^notAfter

Wenn der Server SNI verwendet, müssen Sie das folgende -servernameArgument openssl s_client -servername example.com -connect example.com:443
Flimm

1

Hier ist eine einzeilige Version der akzeptierten Antwort, die nur die verbleibende Anzahl von Tagen ausgibt:

( export DOMAIN=example.com; echo $(( ( $(date +%s -d "$( echo | openssl s_client -servername $DOMAIN -connect $DOMAIN:443 2>/dev/null | openssl x509 -noout -enddate | sed 's/.*notAfter=\(.*\)$/\1/g' )" ) - $(date +%s) ) / 86400 )) )

Beispiel mit www.github.com:

$ ( export DOMAIN=www.github.com; echo $(( ( $(date +%s -d "$( echo | openssl s_client -servername $DOMAIN -connect $DOMAIN:443 2>/dev/null | openssl x509 -noout -enddate | sed 's/.*notAfter=\(.*\)$/\1/g' )" ) - $(date +%s) ) / 86400 )) )
210

Ich bekomme Syntaxfehler in der Nähe von unerwartetem Token "Export"
user1130176

@ user1130176 Die ( ... )Syntax der Subshell ist möglicherweise spezifisch für Bash. Ich vermute, Sie verwenden eine andere Shell?
Mathieu Rey

0

Geben Sie eine Liste der Hostnamen mit Port 443 im Format Hostname: Port in der Datei an und geben Sie sie als Dateinamen an.

! / bin / bash

Dateiname = / root / kns / certs

date1 = $ (date | cut -d "" -f2,3,6)

currentDate = $ (Datum -d "$ date1" + "% Y% m% d")

während read -r line do

dcert = $ (echo | openssl s_client -servername $ line -connect $ line 2> / dev / null | openssl x509 -noout -dates | grep notAfter | cut -d = -f2)

Echo-Hostname: $ line endDate = $ (Datum -d "$ dcert" + "% Y% m% d")

d1 = $ (Datum -d "$ endDate" +% s) d2 = $ (Datum -d "$ currentDate" +% s) Echo Hostname: $ line - verbleibende Tage $ (((d1 - d2) / 86400))

echo $ dcert done <"$ filename"

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.