Playbooks to a new Lilik
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

125 lines
3.4 KiB

  1. #! /usr/bin/env python3
  2. from datetime import datetime
  3. import string
  4. import subprocess
  5. from ansible.module_utils.basic import *
  6. __doc__ = '''
  7. module: ssh_cert
  8. author: Edoardo Putti
  9. short_description: Check ssh certificate validity
  10. '''
  11. CERT_TIME_FORMAT = "%Y-%m-%dT%H:%M:%S"
  12. def serial(lines):
  13. for l in lines:
  14. if l.startswith('Serial'):
  15. return int(l.split().pop(), 10)
  16. def signin_ca(lines):
  17. for l in lines:
  18. if l.startswith('Signing CA'):
  19. # return l.split().pop()
  20. # Starting from OpenSSH v8 the output format of ssh-keygen
  21. # has changed, this should work for all versions:
  22. return l.split()[3]
  23. def still_valid(cert_timestamps):
  24. t = datetime.datetime.today()
  25. return t < cert_timestamps['valid']['to'] and t > cert_timestamps['valid']['from']
  26. def expired(cert_timestamps):
  27. t = datetime.datetime.today()
  28. return t > cert_timestamps['valid']['to']
  29. def not_valid(cert_timestamps):
  30. t = datetime.datetime.today()
  31. return t < cert_timestamps['valid']['from']
  32. def cert_type(lines):
  33. for l in lines:
  34. if l.startswith('Type'):
  35. return l.split(maxsplit=2)[1:]
  36. def valid_from(lines):
  37. for l in lines:
  38. if l.startswith('Valid'):
  39. return datetime.datetime.strptime(l.split()[2], CERT_TIME_FORMAT)
  40. def valid_to(lines):
  41. for l in lines:
  42. if l.startswith('Valid'):
  43. return datetime.datetime.strptime(l.split()[4], CERT_TIME_FORMAT)
  44. def main():
  45. module = AnsibleModule(
  46. argument_spec=dict(),
  47. supports_check_mode=False,
  48. )
  49. result = {}
  50. result['rc'] = 0
  51. result['failed'] = False
  52. result['ca'] = {}
  53. result['ca']['path'] = '/etc/ssh/user_ca.pub'
  54. result['certificate'] = {}
  55. result['certificate']['path'] = '/etc/ssh/ssh_host_ed25519_key-cert.pub'
  56. ca_output = subprocess.check_output([
  57. 'ssh-keygen',
  58. '-l',
  59. '-f', result['ca']['path'],
  60. ])
  61. # If multiple CA are present verify cert against the first one
  62. ca_output = ca_output.splitlines()[0]
  63. ca_lines = ca_output.decode().split(maxsplit=2)
  64. result['ca']['fingerprint'] = ca_lines[1]
  65. result['ca']['comment'] = ca_lines[2]
  66. cert_output = subprocess.check_output([
  67. 'ssh-keygen',
  68. '-L',
  69. '-f', result['certificate']['path'],
  70. ])
  71. cert_lines = [line.strip() for line in cert_output.decode().split('\n')]
  72. result['certificate']['signin_ca'] = signin_ca(cert_lines)
  73. result['certificate']['valid'] = {
  74. 'from': valid_from(cert_lines),
  75. 'to': valid_to(cert_lines),
  76. }
  77. if not still_valid(result['certificate']):
  78. result['failed'] = True
  79. result['msg'] = 'The certificate is not valid now'
  80. if not_valid(result['certificate']):
  81. result['rc'] = 2
  82. if expired(result['certificate']):
  83. result['rc'] = 3
  84. result['certificate']['serial'] = serial(cert_lines)
  85. result['certificate']['type'] = cert_type(cert_lines)
  86. if not result['certificate']['signin_ca'] == result['ca']['fingerprint']:
  87. result['failed'] = True
  88. result['msg'] = 'The provided CA did not sign the certificate specified'
  89. result['rc'] = 1
  90. module.exit_json(**result)
  91. if __name__ == '__main__':
  92. main()