Vérifier la date d'expiration du certificat SSL pour plusieurs serveurs distants


18

Je peux connaître la date d'expiration des certificats SSL en utilisant cette commande OpenSSL:

openssl x509 -noout -in <filename> -enddate

Mais si les certificats sont dispersés sur différents serveurs Web, comment trouvez-vous les dates d'expiration de tous ces certificats sur tous les serveurs?

Il semble y avoir un moyen de se connecter à un autre hôte, mais je ne sais pas comment obtenir la date d'expiration en utilisant ceci:

openssl s_client -connect host:port

Réponses:


15

J'ai eu le même problème et j'ai écrit ceci ... C'est rapide et sale, mais ça devrait marcher. Il enregistrera (et imprimera à l'écran avec le débogage activé) tous les certificats qui ne sont pas encore valides ou expirent dans les 90 prochains jours. Peut contenir des bugs, mais n'hésitez pas à le ranger.

#!/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

Si vous utilisez sous OS X, vous pouvez constater que la datecommande ne fonctionne pas correctement. Cela est dû aux différences entre les versions Unix et Linux de cet utilitaire. Le message lié a des options pour que cela fonctionne.


J'ai un peu modifié / développé votre script pour pouvoir également vérifier les certificats du serveur de messagerie ainsi que pour donner un bon aperçu de l'état de tous les certificats. Vous pouvez trouver le script modifié à: gist.github.com/lkiesow/c9c5d96ecb71822b82cd9d194c581cc8
Lars Kiesow

1
Si le serveur utilise SNI, vous devez inclure l' -servernameargument, comme ceci:openssl s_client -servername example.com -connect example.com:443
Flimm

11

Exécutez simplement la commande ci-dessous et elle fournira la date d'expiration:

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

Vous pouvez utiliser cette commande dans un fichier de commandes pour collecter ces informations pour des serveurs plus distants.


2
La façon dont vous avez écrit ceci, vous devez appuyer sur CTRL-C pour y mettre fin. Vous pouvez résoudre ce problème avec: openssl s_client -connect google.com.br:443 </ dev / null 2> & 1 | openssl x509 -noout -enddate Juste une pensée.
numberwhun

1
Si le serveur utilise SNI, vous devez utiliser l' -servernameargument, comme ceci:openssl s_client -servername google.com.br -connect google.com.br:443
Flimm

6

Ci-dessous est mon script qui sert de chèque dans les nagios. Il se connecte à un hôte spécifique, il vérifie que le certificat est valide dans un seuil défini par les options -c / -w. Il peut vérifier que le CN du certificat correspond au nom que vous attendez.

Vous avez besoin de la bibliothèque python openssl, et j'ai fait tous les tests avec python 2.7.

Il serait trivial qu'un script shell l'appelle plusieurs fois. Le script renvoie les valeurs de sortie nagios standard pour l'état critique / avertissement / ok.

Une simple vérification du certificat de Google peut être effectuée comme ceci.

./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

Connectez-vous à host: port, extrayez le certificat avec sed et écrivez-le dans /tmp/host.port.pem.

get_expiration_date

Lisez le fichier pem donné et évaluez la clé notAfter en tant que variable bash. Imprimez ensuite le nom du fichier et la date à laquelle il expire dans un environnement local donné.

get_pem_expiration_dates

Itérer un fichier d'entrée et exécuter les fonctions ci-dessus.

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

exemple de sortie

 $ 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

Et pour répondre à votre question:

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

Si le serveur utilise SNI, vous devez inclure l' -servernameargument, comme ceci:openssl s_client -servername example.com -connect example.com:443
Flimm

1

Voici une version à une ligne de la réponse acceptée, qui affiche simplement le nombre de jours restant:

( 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 )) )

Exemple avec 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

j'obtiens une erreur de syntaxe près du jeton inattendu `export '
user1130176

@ user1130176 la ( ... )syntaxe du sous - shell peut être spécifique à Bash; Je suppose que vous utilisez un autre shell?
Mathieu Rey

0

Donnez la liste des noms d'hôtes avec le port 443 au format nom d'hôte: port dans le fichier et donnez-le comme nom de fichier.

! / bin / bash

nom de fichier = / root / kns / certs

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

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

tandis que la ligne de lecture -r fait

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

echo Nom d'hôte: $ line endDate = $ (date -d "$ dcert" + "% Y% m% d")

d1 = $ (date -d "$ endDate" +% s) d2 = $ (date -d "$ currentDate" +% s) echo Hostname: $ line - Days Remaining $ ((((d1 - d2) / 86400))

echo $ dcert done <"$ filename"

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.