ตรวจสอบวันหมดอายุของใบรับรอง SSL สำหรับเซิร์ฟเวอร์ระยะไกลหลายเครื่อง


18

ฉันสามารถหาวันหมดอายุของใบรับรอง SSL โดยใช้คำสั่ง OpenSSL นี้:

openssl x509 -noout -in <filename> -enddate

แต่ถ้าใบรับรองกระจัดกระจายบนเว็บเซิร์ฟเวอร์ที่แตกต่างกันคุณจะค้นหาวันหมดอายุของใบรับรองเหล่านี้ทั้งหมดในเซิร์ฟเวอร์ทั้งหมดได้อย่างไร

ดูเหมือนจะมีวิธีการเชื่อมต่อกับโฮสต์อื่น ๆ แต่ฉันไม่แน่ใจว่าจะได้รับวันหมดอายุโดยใช้สิ่งนี้:

openssl s_client -connect host:port

คำตอบ:


15

ฉันมีปัญหาเดียวกันและเขียนสิ่งนี้ ... มันเร็วและสกปรก แต่ควรใช้งานได้ มันจะเข้าสู่ระบบ (และพิมพ์ไปที่หน้าจอโดยมีการดีบัก) certs ใด ๆ ที่ยังไม่ถูกต้องหรือหมดอายุใน 90 วันถัดไป อาจมีข้อบกพร่องบางอย่าง แต่อย่าลังเลที่จะทำความสะอาด

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

หากใช้บน OS X คุณอาจพบว่าdateคำสั่งนั้นทำงานไม่ถูกต้อง นี่คือสาเหตุที่แตกต่างในยูทิลิตี้รุ่น Unix และ Linux โพสต์ที่เชื่อมโยงมีตัวเลือกสำหรับการทำงานนี้


ฉันแก้ไข / ขยายสคริปต์ของคุณเล็กน้อยเพื่อให้สามารถตรวจสอบใบรับรองเซิร์ฟเวอร์อีเมลรวมทั้งให้ภาพรวมที่ดีเกี่ยวกับสถานะของใบรับรองทั้งหมด คุณสามารถค้นหาสคริปต์ที่ปรับเปลี่ยนได้ที่: gist.github.com/lkiesow/c9c5d96ecb71822b82cd9d194c581cc8
Lars Kiesow

1
หากเซิร์ฟเวอร์กำลังใช้ SNI คุณต้องรวม-servernameอาร์กิวเมนต์เช่นนี้:openssl s_client -servername example.com -connect example.com:443
Flimm

11

เพียงแค่เรียกใช้คำสั่งด้านล่างและมันจะให้วันที่หมดอายุ:

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

คุณสามารถใช้คำสั่งนี้เป็นแบตช์ไฟล์เพื่อรวบรวมข้อมูลนี้สำหรับเซิร์ฟเวอร์ระยะไกลเพิ่มเติม


2
วิธีที่คุณเขียนสิ่งนี้คุณต้องกด CTRL-C เพื่อจบมัน คุณสามารถแก้ไขได้ด้วย: openssl s_client -connect google.com.br:443 </ dev / null 2> & 1 | openssl x509 ไม่ จำกัด วันเพียงแค่คิด
numberwhun

1
หากเซิร์ฟเวอร์ใช้ SNI คุณต้องใช้-servernameอาร์กิวเมนต์เช่นนี้:openssl s_client -servername google.com.br -connect google.com.br:443
Flimm

6

ด้านล่างเป็นสคริปต์ของฉันที่ตรวจสอบภายใน nagios มันเชื่อมต่อกับโฮสต์ที่เฉพาะเจาะจงจะตรวจสอบว่าใบรับรองถูกต้องภายในเกณฑ์ที่กำหนดโดยตัวเลือก -c / -w สามารถตรวจสอบว่า CN ของใบรับรองตรงกับชื่อที่คุณคาดหวัง

คุณต้องมีไลบรารี python openssl และฉันทำการทดสอบด้วย python 2.7 ทั้งหมด

มันจะไม่สำคัญที่เชลล์สคริปต์จะเรียกสิ่งนี้หลายครั้ง สคริปต์ส่งคืนค่าการออกจาก nagios มาตรฐานสำหรับสถานะ critical / warning / ok

การตรวจสอบใบรับรองของ Google อย่างง่ายสามารถทำได้เช่นนี้

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

เชื่อมต่อกับโฮสต์: พอร์ตแยกใบรับรองด้วย sed แล้วเขียนลงใน /tmp/host.port.pem

get_expiration_date

อ่านไฟล์ pem ที่กำหนดและประเมินคีย์ notAfter เป็นตัวแปร bash จากนั้นพิมพ์ชื่อไฟล์และวันที่เมื่อมันหมดอายุในสถานที่ที่กำหนด

get_pem_expiration_dates

ทำซ้ำอินพุตไฟล์และเรียกใช้ฟังก์ชันข้างต้น

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

ตัวอย่างผลลัพธ์

 $ 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

และเพื่อตอบคำถามของคุณ:

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

หากเซิร์ฟเวอร์ใช้ SNI คุณจะต้องรวม-servernameอาร์กิวเมนต์เช่นนี้:openssl s_client -servername example.com -connect example.com:443
Flimm

1

ต่อไปนี้เป็นคำตอบที่ยอมรับรุ่นเดียวซึ่งจะแสดงจำนวนวันที่เหลืออยู่:

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

ตัวอย่างกับ 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

ฉันได้รับข้อผิดพลาดทางไวยากรณ์ใกล้โทเค็น `ส่งออก 'ที่ไม่คาดคิด
user1130176

@ user1130176 ( ... )ไวยากรณ์ subshell อาจเฉพาะเจาะจงกับ Bash; ฉันเดาว่าคุณกำลังใช้เปลือกที่แตกต่างกันใช่ไหม
Mathieu Rey

0

ให้รายชื่อโฮสต์ด้วยพอร์ต 443 ในรูปแบบชื่อโฮสต์: พอร์ตในไฟล์และตั้งเป็นชื่อไฟล์

! / bin / ทุบตี

ชื่อไฟล์ = / ราก / KNS / ใบรับรอง

date1 = $ (วันที่ | cut -d "" -f2,3,6)

currentDate = $ (วันที่ -d "$ date1" + "% Y% m% d%")

ในขณะที่อ่าน -r บรรทัดทำ

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 = $ (วันที่ -d "$ dcert" + "% Y% m% d%")

d1 = $ (วันที่ -d "$ endDate" +% s) d2 = $ (วันที่ -d "$ currentDate" +% s) echo ชื่อโฮสต์: $ line - วันที่เหลือ $ ((d1 - d2) / 86400)

echo $ dcert เรียบร้อยแล้ว <"$ filename"

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.