Verifique a data de validade do certificado SSL para vários servidores remotos


18

Eu posso descobrir a data de validade dos certificados SSL usando este comando OpenSSL:

openssl x509 -noout -in <filename> -enddate

Mas se os certificados estão espalhados em diferentes servidores da Web, como você encontra as datas de validade de todos esses certificados em todos os servidores?

Parece haver uma maneira de conectar-se a outro host, mas não sei como obter a data de validade usando este:

openssl s_client -connect host:port

Respostas:


15

Eu tive o mesmo problema e escrevi isso ... É rápido e sujo, mas deve funcionar. Ele registra (e imprime na tela com a depuração) quaisquer documentos que ainda não são válidos ou expiram nos próximos 90 dias. Pode conter alguns bugs, mas fique à vontade para arrumá-lo.

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

Se estiver usando no OS X, você poderá descobrir que o datecomando não funciona corretamente. Isso ocorre devido a diferenças na versão Unix e Linux deste utilitário. A postagem vinculada tem opções para fazer esse trabalho.


Modifiquei / ampliei um pouco o seu script para também poder verificar os certificados do servidor de correio, bem como fornecer uma boa visão geral sobre o status de todos os certificados. Você pode encontrar o script modificado em: gist.github.com/lkiesow/c9c5d96ecb71822b82cd9d194c581cc8
Lars Kiesow

1
Se o servidor estiver usando SNI, você precisará incluir o -servernameargumento da seguinte maneira:openssl s_client -servername example.com -connect example.com:443
Flimm

11

Basta executar o comando abaixo e ele fornecerá a data de validade:

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

Você pode usar este comando em um arquivo em lotes, para reunir essas informações para mais servidores remotos.


2
Do jeito que você escreveu isso, você deve pressionar CTRL-C para finalizar. Você pode corrigir isso com: openssl s_client -connect google.com.br:443 </ dev / null 2> & 1 | openssl x509 -noout -enddate Apenas um pensamento.
numberwhun

1
Se o servidor usa SNI, você precisa usar o -servernameargumento da seguinte maneira:openssl s_client -servername google.com.br -connect google.com.br:443
Flimm

6

Abaixo está o meu script que, como uma verificação dentro dos nagios. Ele se conecta a um host específico, verifica se o certificado é válido dentro de um limite definido pelas opções -c / -w. Pode verificar se o CN do certificado corresponde ao nome que você espera.

Você precisa da biblioteca openssl do python, e eu fiz todos os testes com o python 2.7.

Seria trivial ter um script de shell chamar isso várias vezes. O script retorna os valores de saída padrão dos nagios para o status crítico / aviso / ok.

Uma simples verificação do certificado do Google pode ser realizada assim.

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

Conecte-se ao host: port, extraia o certificado com sed e grave-o em /tmp/host.port.pem.

get_expiration_date

Leia o arquivo pem fornecido e avalie a chave notAfter como uma variável bash. Em seguida, imprima o nome do arquivo e a data em que ele expira em um determinado local.

get_pem_expiration_dates

Itere algum arquivo de entrada e execute as funções acima.

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

saída de amostra

 $ 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

E para responder sua pergunta:

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

Se o servidor estiver usando SNI, você precisará incluir o -servernameargumento da seguinte maneira:openssl s_client -servername example.com -connect example.com:443
Flimm

1

Aqui está uma versão de uma linha da resposta aceita, que apenas gera o número restante de dias:

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

Exemplo com 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

eu recebo erro de sintaxe próximo ao inesperado token `export '
user1130176 09/12

@ user1130176 a ( ... )sintaxe do subshell pode ser específica do Bash; Eu acho que você está usando um shell diferente?
Mathieu Rey

0

Forneça uma lista de nomes de host com a porta 443 no formato hostname: port no arquivo e dê-o como nome do arquivo.

! / bin / bash

filename = / root / kns / certs

date1 = $ (data | corte -d "" -f2,3,6)

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

enquanto lê a linha -r

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

echo Nome do host: $ line endDate = $ (data-d "$ dcert" + "% Y% m% d")

d1 = $ (data -d "$ endDate" +% s) d2 = $ (data -d "$ currentDate" +% s) eco Nome do host: $ line - Dias restantes $ (((d1 - d2) / 86400))

echo $ dcert done <"$ filename"

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.