Création de l'entité temps restant d'un certificat SSL

Bonjour à tous.

Depuis les dernières versions, le décompte de validation du certificat SSL n’est plus implémenté lorsque nous ajoutons l’intégration: « Expiration du certificat ».

Nous n’avons maintenant qu’un seul sensor nous indiquant la date d’expiration de notre certificat, sous la forme :

« sensor.cert_expiry_timestamp_URLDUDOMAINE »

Ce n’étais pas le cas avant. Une modification a eu lieu, afin d’éviter un contrôle continu du temps présent. Nous avions un autre sensor avec le décompte en jour (par exemple : 13 jours restants). Nous pouvions alors facilement créer une automatisation qui nous permettait d’avoir une notification si le certificat expirait sous peu.

Afin de recréer ce sensor, j’ai cherché avec l’aide de @Clemalex, comment déterminer le nombre de jour restant entre la date d’aujourd’hui, et la date d’expiration. Toutes la recherche (intéressante lorsque l’on veut comprendre la démarche de réflexion) se trouve dans le reste du post.
Je vais m’attarder ici sur le résultat.

PRE REQUIS

Pour commencer, il nous faut évidemment un certificat SSL, puis l’intégration (que vous pouvez ajouter via l’UI). Vous aurez donc la création du premier sensor.
Il nous faudra aussi un sensor date.
Le problème avec l’ancienne méthode, c’est qu’elle ne correspondait pas à une utilisation économe des performances du serveur. Le sensor était mis à jour à chaque changement de temps. Le sensor date, lui ne se met a jour qu’une fois par jour.
Ajoutez dans vos sensors l’exemple suivant:

sensor:
  - platform: time_date
    display_options:
      - 'date'

Vous aurez donc 2 sensors :

  1. states('sensor.cert_expiry_timestamp_URLDUDOMAINE') équivalent à un chaine de caractère représentant la date d’expiration du certificat au format :

« ANNEE-MOIS-JOURTHEURE:MINUTE:SECONDE+DECALAGE HORAIRE » (vous noterez la présence du « T » pour séparer la date et l’heure, et le + pour séparé l’heure du décalage horaire).

  1. states('sensor.date') équivalent à une chaine de caractère représentant la date du jour au format:

« ANNEE-MOIS-JOUR ».

CALCUL

Pour trouver le nombre de jour entre ces 2 sensors, nous devrons convertir les chaines de caractères en format « datetime », puis effectuer la soustraction. Comme nous serons en format « datetime », nous pourrons récupérer le nombre de jour du résultat.

  1. Pour ce faire, nous avons besoin de maitriser les formats des deux sensor à l’aide de la fonction :
    strptime (je vous laisse lire le chapitre TIME de la docs) :
strptime(states('sensor.cert_expiry_timestamp_URLDUDOMAINE'), '%Y-%m-%dT%H:%M:%S+00:00')
strptime(states('sensor.date'), "%Y-%m-%d")

Ici, nous indiquons pour les deux sensors, ou se trouve l’année, le mois, le jour, etc dans leur chaine de caractère.

  1. Les deux sensors étant maintenant au format « datetime », nous pouvons les soustraires, et recuperer l’attribut « jour » du résultat :

(strptime(states('sensor.cert_expiry_timestamp_URLDUDOMAINE'), '%Y-%m-%dT%H:%M:%S+00:00')-strptime(states('sensor.date'), "%Y-%m-%d")).days
Ici, soustraction des deux dates, puis récupération de l’attribut « jour » à l’aide de : .days

SENSOR
Je crée donc le template sensor de la forme suivante:

  1. Id Unique: cert_expiry_delay_URLDUDOMAINE
  2. Unité de mesure: Jour(s)
  3. Icone : un sablier (mdi:time-sand)
  4. Valeur du template: {{(strptime(states('sensor.cert_expiry_timestamp_URLDUDOMAINE'), '%Y-%m-%dT%H:%M:%S+00:00')-strptime(states('sensor.date'), "%Y-%m-%d")).days}}
sensor:
  - platform: template
    sensors:
      cert_expiry_delay_URLDUDOMAINE:
        friendly_name: "Expiration du certificat"
        unit_of_measurement: "Jour(s)"
        icon_template: mdi:timer-sand
        value_template: >-
          {{(strptime(states('sensor.cert_expiry_timestamp_URLDUDOMAINE'), '%Y-%m-%dT%H:%M:%S+00:00')-strptime(states('sensor.date'), "%Y-%m-%d")).days}}

AUTOMATISATION
Les 2 sensors sont créés. Nous aurons donc chaque jour une mise à jour unique du délai restant. Nous pouvons donc créer une automatisation, via l’UI si vous le souhaitez, du type :

Si sensor.cert_expiry_delay_URLDUDOMAINE passe en dessous de 15, alors envoyer une notification.

alias: Expiration du certificat
description: ''
mode: single
trigger:
  - platform: numeric_state
    entity_id: sensor.cert_expiry_delay_URLDUDOMAINE
    below: '15'
condition: []
action:
  - service: notify.mobile_app_iphone
    data:
      title: Expiration du certificat
      message: Le certificat arrive à expiration dans {{states('sensor.cert_expiry_delay_URLDUDOMAINE')}} jour(s).

Vous trouverez plus bas, d’autre possibilité pour l’automatisation.

Si vous souhaitez simplement avoir une automatisation, sans créer d’entité, rendez vous directement au post de @Clemalex.

La commande suivante retourne le nombre de jours restants

ssl-cert-check -b -c chemin vers le fichier .pem | awk '{print $NF}'
1 « J'aime »

Voici un déclencheur qui devrait fonctionner (se déclenche à minuit, tous les jours, 15 jours avant, ajouter un delay si t’as pas envie d’être réveillé :wink:) en calculant à partir de la date actuelle du serveur :

trigger:
    - platform: template
      value_template: >-
       {{ as_timestamp(strptime(states("sensor.date"),"%Y-%m-%d") + timedelta(days=15) ) | timestamp_custom("%Y-%m-%d") >= states("sensor.cert_expiry_timestamp_NOM_DE_DOMAINE").split('T')[0] }}

Il faut avoir activé l’entité date de cette plateforme.
Pour les explications, il faudra attendre après le/les réveillons :innocent:

Bon réveillon !

PS:
l’équivalent en calculant dans l’autre sens, à partir de la date du certificat :

{{as_timestamp(strptime(states("sensor.cert_expiry_timestamp_NOM_DE_DOMAINE").split("T")[0],"%Y-%m-%d") - timedelta(days=15)) | timestamp_custom("%Y-%m-%d") <= states("sensor.date")}}

et à partir de l’entité sensor.time :

{{ as_timestamp(states.sensor.time.last_changed + timedelta(days=15) ) | timestamp_custom("%Y-%m-%d") >= states("sensor.cert_expiry_timestamp_NOM_DE_DOMAINE").split('T')[0] }}

Au final, je ferais ainsi :

Ce que l’on cherche à faire :

On a une entité qui contient la date de fin de la validité du certificat SSL pour se connecter de manière sécurisée à un serveur, et on souhaite être prévenu 15 jours avant (puis les suivants jusqu’à la date fatidique si pas renouvelé).

Pré-requis

On va utiliser la date du jour du serveur (documentation).
Dans notre fichier de configuration, il faut ajouter :

sensor:
  - platform: time_date
    display_options:
      - 'date' # crée une entité représentant la date du jour

L’entité contenant l’information

Comme nous avons ajouter l’intégration Certificate Expiry , nous avons une entité représentant la date d’expiration du certificat dont nous voulons être averti de la péremption prochaine (à 15 jours).

Entité: sensor.cert_expiry_timestamp_NOM_DE_DOMAINE
Etat: 2021-03-17T18:47:39+00:00

Bien que ce format semble être de la classe datetime, il n’en est rien.

Ce n’est pas grave, on va nous même convertir ce texte, de classe string, vers la classe datetime afin de pouvoir faire des calculs avec la notion de temps (+/- 15 jours, heures, minutes, etc.)

Conversion en datetime

Dans l’éditeur de modèle (présent dans l’onglet Modèle de la vue Options de développement), si on tape :

{{states('sensor.cert_expiry_timestamp_NOM_DE_DOMAINE')}}

On récupère l’état sous forme de texte (string)… il nous faut le convertir en classe datetime (documentation):

{{ strptime(states('sensor.cert_expiry_timestamp_NOM_DE_DOMAINE'), '%Y-%m-%dT%H:%M:%S+00:00') }}

Avec ce code, on récupère bien un objet de la classe datetime auquel on peux faire des calculs de temps.

Dans notre cas, nous avons dit que nous aimerions être averti 15 jours avant la date d’expiration, il nous faut donc calculer la date d’expiration moins 15 jours. C’est possible de façon très facile avec la fonction timedelta (documentation) :

{{strptime(states('sensor.cert_expiry_timestamp_NOM_DE_DOMAINE'), '%Y-%m-%dT%H:%M:%S+00:00') - timedelta(days=15)}}

On vient donc de trouver la formule qui nous permet d’obtenir la date à partir de laquelle nous voulons être avertie de la future expiration.

Il nous reste donc à être averti.

L’automatisation

Pour être averti, cela va passer par une automatisation.
Pour l’action, je vous laisse choisir votre plateforme préférée et l’ajouter dans la partie action de l’automatisation.
Je vais me consacrer ici aux déclencheurs et conditions de l’automatisation.

Déclencheur de l’automatisation

Pour rappel, nous voulons être averti 15 jours avant la date d’expiration du certificat, ce sera donc notre déclencheur.
En français cela donne donc :

Est-ce que la date d’aujourd’hui est égale ou supérieure à la date d’expiration du certificat - 15 jours ?

La version code donne donc :

{{ states('sensor.date') >= strptime(states('sensor.cert_expiry_timestamp_NOM_DE_DOMAINE'), '%Y-%m-%dT%H:%M:%S+00:00') - timedelta(days=15)}}

:warning: Pour qu’une automatisation se déclenche, il faut que l’un de ses déclencheurs (nous, nous n’en avons qu’un ici) renvoit l’état Vrai.
Mais notre code renverra constamment Faux. Pourquoi ? Car l’état de l’entité sensor.date a le format ANNEE-MOIS-JOUR, et on le compare à un objet ayant le format ANNEE-MOIS-DATE HH:MM:SS.
Il faut donc raccourcir notre objet afin d’avoir le même format que l’entité sensor.date.

Pour cela, on va utiliser la fonction as_timestamp (documentation) qui permet de convertir en timestamp afin de pourvoit appliquer le filtre : timestamp_custom (documentation).

Pour rappel, le code suivant renvoi la date à laquelle doivent commencer les notifications :

{{strptime(states('sensor.cert_expiry_timestamp_NOM_DE_DOMAINE'), '%Y-%m-%dT%H:%M:%S+00:00') - timedelta(days=15)}}

renvoi (par exemple avec mon certificat - 15jours) :

2021-03-02 18:47:39

On va donc raccourcir le retour d’état avec ce code :

{{as_timestamp(strptime(states('sensor.cert_expiry_timestamp_NOM_DE_DOMAINE'), '%Y-%m-%dT%H:%M:%S+00:00') - timedelta(days=15)) | timestamp_custom('%Y-%m-%d')}}

qui renvoi :

2021-03-02

Bingo ! On va pouvoir comparer ce résultat avec l’entité sensor.date:+1:

Nous pouvons donc maintenant écrire le déclencheur de l’automatisation ainsi :

{{ states('sensor.date') >= as_timestamp(strptime(states('sensor.cert_expiry_timestamp_NOM_DE_DOMAINE'), '%Y-%m-%dT%H:%M:%S+00:00') - timedelta(days=15)) | timestamp_custom('%Y-%m-%d')}}

Malheureusement, si on met ce code dans le déclencheur de l’automatisation, nous ne serons notifié qu’une seule fois.
Pourquoi ? Car une fois de que la date sera à 15 jours de l’expiration, le résultat du modèle va passer à Vrai et ce, jusqu’à ce que la date d’expiration du certificat repasse au dessus du seuil des 15 jours.

Donc nous devons utiliser ce code comme condition de l’automatisation et non pas comme déclencheur.
Et le déclencheur alors ? Et bien on va faire déclencher l’automatisation tous les jours avec le code suivant :

trigger:
    - platform: state
      entity_id: sensor.date

Condition de l’automatisation

L’automatisation va donc s’exécuter à chaque changement d’état de l’entité sensor.date (donc tous les jours à minuit).
On va donc conditionner l’exécution des actions sur le fait d’être dans la période des 15 jours avant l’expiration :

condition:  "{{ states('sensor.date') >= as_timestamp(strptime(states('sensor.cert_expiry_timestamp_NOM_DE_DOMAINE'), '%Y-%m-%dT%H:%M:%S+00:00') - timedelta(days=15)) | timestamp_custom('%Y-%m-%d')}}"

Et voila !

Pour résumer

Voici donc le code complet de l’automatisation :

automation:
  - alias: 'alerte_expiration_certificat'
    id: 'alerte_expiration_certificat'
    trigger:
    - platform: homeassistant
      event: start
    - platform: state
      entity_id: sensor.date
    condition: "{{ states('sensor.date') >= as_timestamp(strptime(states('sensor.cert_expiry_timestamp_<NOM_DE_DOMAINE>'), '%Y-%m-%dT%H:%M:%S+00:00') - timedelta(days=states('input_number.ssl_seuil')|int)) | timestamp_custom('%Y-%m-%d')}}"
    action:
      - service: persistent_notification.create
        data:
          message: "Le certificat pour NOM_DE_DOMAINE est bientôt expiré ! \n Merci de le renouveller !"
          title: "Certificat SSL/TLS"
          notification_id: "{{ (range(1, 9999)|random) }}"

Alors oui, j’ai ajouter un déclencheur :

    - platform: homeassistant
      event: start

qui correspond au démarrage du serveur afin de recevoir une notification en cas de démarrage sans attendre le jour d’après (imaginez un redémarrage à 23:59 et le serveur est opérationnel à 00:01, vous loupez une journée :scream: :wink:)

Alors re-oui, j’ai implémenté le fait de gérer le seuil d’alerte (les fameux 15 jours) par l’entité input_number.ssl_seuil :

states('input_number.ssl_seuil')|int

afin de rendre l’édition du seuil possible par l’interface graphique. Si vous ne savez pas implémenter cette entité, demandez dans les commentaires :+1:.

Alors non, je n’ai pas voulu ajouter les lignes :

    - platform: state
      entity_id: input_number.ssl_seuil

afin de déclencher l’automatisation également sur le changement d’état de l’entité gérant le seuil :wink:.

Voilà, mes explications sont finies.
Si vous avez des questions…n’hésitez pas :+1:

1 « J'aime »

Merci @Clemalex. C’est impressionnant de détails. Si on pouvait tous faire comme cela, on y gagnerait…

Merci de ton retour. Je continue de chercher de mon coté, car je cherche à le faire dans l’autre sens:
Creer un sensor avec le nombre de jour restant, pour pouvoir l’afficher sur le lovelace, et creer l’automatisation a partir de là.

Bon, j’avance petit à petit, j’ai vraiment eu du mal a me situer entre les datetimes, et les string. J’ai enfin une valeure en datetime avec: .

(strptime(states('sensor.cert_expiry_timestamp_URL_DU_DOMAINE'), '%Y-%m-%dT%H:%M:%S+00:00')
-
strptime((states('sensor.date')), "%Y-%m-%d"))

Il ne me manque maintenant plus qu’a trouver comment ne récuperer que le nombre de jour (les heures etant aussi affichées)

1 « J'aime »

T’es pas loin… :drooling_face:

1 « J'aime »

Je te laisse chercher ou je dis comment faire ?

Ne pas ouvrir si tu veux chercher toi-même !

Quand je disais que tu n’étais pas loin, c’est vraiment le cas.

En fait, ton code correspond à cet exercice :wink:

Ton code calcul un delta entre deux dates car tu as eu la bonne idée de convertir tout ce petit monde en objet datetime, du coup, les calculs de date deviennent possible :+1:.

Du coup, il faut simplement rajouter que tu veux juste récupérer le nombre de jours en ajoutant .days

Ce qui donne comme nouvelle entité :

sensor:
  - platform: template
    sensors:
      cert_days:
        friendly_name: "Nombre de jour restant"
        value_template: >
          {{(strptime(states('sensor.cert_expiry_timestamp_URL_DU_DOMAINE'), '%Y-%m-%dT%H:%M:%S+00:00')
          -
          strptime((states('sensor.date')), "%Y-%m-%d")).days }}

N’oublies pas de poster l’automatisation qui va avec :smiling_face_with_three_hearts:

Pfffiouuuuuu Des heures et des heures, pour arriver a cette conclusion, en sachant que j’etais deja passé par cette case la .day, et .day() :sob: :sob: :sob: :rofl: :rofl: :rofl: tout ca pour un « s » lol. Bon merci pour ton partage, j’ai reussi en tombant sur un site a trouver ce .days aussi. Je vais modifier mon poste support, en poste tutoriel, et indiquer comment creer le sensor, et l’automatisation qui ira avec.

C’était pas de la tarte moi qui n’y connais rien en python…

Oui, je suis d’accord :smiling_face_with_three_hearts:.

Oui, j’avais compris que tu souhaitais retrouver l’entité que tu as perdue suite à la mise à jour…

Mais j’avais un besoin de tutoriel… :upside_down_face:

Au final, n’oublie pas de marquer la solution qui te conviens le mieux, ceux qui veulent comprendre feront défiler les posts un à un… :grinning:

Par contre, je l’ai compris, mais je ne t’ai pas vu expliquer ca.

Modifie le premier post comme tu le dit et je ferais le ménage dans les messages pour extraire mon tutoriel (qui ne réponds pas réellement à ton besoin) en pointant sur le tien :+1:

Comme tu veux, mais je pense que tout ce qu’on as fait montre la démarche, et des fois c’est plus explicite…

Oups ! j’édites car ça ne sert pas.

Je voulais voir si en ne récupérant que la date, sans les heures, je pouvais introduire cette explication… :grin:

Oui mais ca reste intéressant. La fonction split peut resservir plus tard pour autre chose, du coup de la trouver par hasard sur un tel poste pourra aider pour une autre recherche. Perso moi je me la met de coté.

Dès fois, j’ai la mémoire courte, j’ai expliqué la function split ici :
https://forum.hacf.fr/t/automatisation-avec-input-number-cover/1320/33?u=clemalex

1 « J'aime »

Je suis à :100: d’accord, le plus important n’est pas le code mais la démarche et la réflexion pour y arriver !

Ca marche, je laisse ici :+1:

1 « J'aime »

Désolé de relancer un si vieux post mais j’ai un soucis pour le mettre en oeuvre.

J’ai mis ce code :

# Création d'un capteur pour avoir le nombre de jours avant expiration du certificat.
#
# https://forum.hacf.fr/t/creation-de-lentite-temps-restant-dun-certificat-ssl/1670
# https://forum.hacf.fr/t/creation-de-lentite-temps-restant-dun-certificat-ssl/1670/4
#
platform: template
sensors:
  cert_expiry_delay_URLDUDOMAINE:
    friendly_name: "Expiration du certificat URLDUDOMAINE"
    unit_of_measurement: "Jour(s)"
    icon_template: mdi:timer-sand
    value_template: >-
      {{(strptime(states('sensor.cert_expiry_timestamp_URLDUDOMAINE'), '%Y-%m-%dT%H:%M:%S+00:00')-strptime(states('sensor.date'), "%Y-%m-%d")).days}}

Et j’ai cette logs dans le journal de HA suite au rédémarrage :

TemplateError('TypeError: unsupported operand type(s) for -: 'str' and 'datetime.datetime'') while processing template 'Template("{{(strptime(states('sensor.cert_expiry_timestamp_URLDUDOMAINE'), '%Y-%m-%dT%H:%M:%S+00:00')-strptime(states('sensor.date'), "%Y-%m-%d")).days}}")' for attribute '_attr_native_value' in entity 'sensor.cert_expiry_delay_URLDUDOMAINE'

Evidemment, je n’ai pas URLDUDOMAINE dans mon code et les logs mais mon nom de domaine :innocent:

J’ai un peu de mal à comprendre la log … :thinking:

Bonjour,

Je suis preneur d’une explication, après recherche, je tourne en rond… Je précise que je suis débutant

Merci par avance

Une explication sur quoi ? Ma réponse comporte déjà énormément de détails…
Donc si tu en veux plus, explique où tu bloques exactement et ce que tu as fais…