Načrtovana obnova

Kompleksnost: Nizka
Trajanje: 30-60 minut za nastavitev
Primer uporabe: Strežniki brez ACME/Kubernetes

Avtomatska obnova certifikatov preko načrtovanih nalog za klasična strežniška okolja.


Arhitektura

flowchart LR subgraph TRIGGER["SPROŽILEC"] C[Cron Job] S[Systemd Timer] T[Task Scheduler] end subgraph CHECK["PREVERJANJE"] E{Veljavnost < 30 dni?} end subgraph RENEW["OBNOVA"] R1[Ustvari CSR] R2[Pridobi podpis] R3[Namesti] R4[Ponovno naloži storitev] end subgraph NOTIFY["OBVESTILO"] N1[E-pošta] N2[Slack/Teams] N3[Zahtevek] end C --> E S --> E T --> E E -->|Da| R1 --> R2 --> R3 --> R4 E -->|Ne| N1 R4 --> N1 R4 --> N2 R4 --> N3 style E fill:#fff3e0 style R3 fill:#e8f5e9


Linux: Cron + Bash

Skripta za obnovo

#!/bin/bash
# /usr/local/bin/cert-renewal.sh
# Avtomatska obnova certifikatov
 
set -euo pipefail
 
# Konfiguracija
CERT_DIR="/etc/ssl/certs"
KEY_DIR="/etc/ssl/private"
CA_SERVER="https://ca.internal.example.com"
RENEWAL_DAYS=30
LOG_FILE="/var/log/cert-renewal.log"
MAIL_TO="pki-team@example.com"
 
log() {
    echo "$(date -Iseconds) $1" >> "$LOG_FILE"
}
 
check_and_renew() {
    local cert_file="$1"
    local key_file="$2"
    local service="$3"
 
    # Preverjanje veljavnosti
    if openssl x509 -checkend $((RENEWAL_DAYS * 86400)) -noout -in "$cert_file" 2>/dev/null; then
        log "INFO: $cert_file še veljaven"
        return 0
    fi
 
    log "WARN: $cert_file poteče, obnavljam..."
 
    # Ekstrakcija subjekta iz starega certifikata
    local subject=$(openssl x509 -in "$cert_file" -subject -noout | sed 's/subject=//')
    local san=$(openssl x509 -in "$cert_file" -text -noout | grep -A1 "Subject Alternative" | tail -1 | tr -d ' ')
 
    # Ustvarjanje novega CSR
    local csr_file=$(mktemp)
    openssl req -new -key "$key_file" -out "$csr_file" -subj "$subject"
 
    # Pošiljanje CSR na CA (primer: REST API)
    local new_cert=$(curl -s -X POST "$CA_SERVER/api/sign" \
        -H "Content-Type: application/x-pem-file" \
        -H "Authorization: Bearer $(cat /etc/ssl/ca-token)" \
        --data-binary @"$csr_file")
 
    if [ -z "$new_cert" ]; then
        log "ERROR: Obnova neuspešna za $cert_file"
        echo "Obnova certifikata neuspešna: $cert_file" | mail -s "CERT RENEWAL FAILED" "$MAIL_TO"
        rm "$csr_file"
        return 1
    fi
 
    # Varnostna kopija in zamenjava
    cp "$cert_file" "${cert_file}.bak.$(date +%Y%m%d)"
    echo "$new_cert" > "$cert_file"
 
    # Ponovni zagon storitve
    if [ -n "$service" ]; then
        systemctl reload "$service" || systemctl restart "$service"
        log "INFO: Storitev $service ponovno naložena"
    fi
 
    rm "$csr_file"
    log "INFO: $cert_file uspešno obnovljen"
 
    # Obvestilo
    echo "Certifikat obnovljen: $cert_file" | mail -s "CERT RENEWED" "$MAIL_TO"
}
 
# Glavna logika
log "=== Preverjanje obnove začeto ==="
 
# Definicija certifikatov: cert_file:key_file:service
CERTS=(
    "/etc/ssl/certs/webserver.pem:/etc/ssl/private/webserver.key:nginx"
    "/etc/ssl/certs/api.pem:/etc/ssl/private/api.key:api-service"
    "/etc/ssl/certs/mail.pem:/etc/ssl/private/mail.key:postfix"
)
 
for cert_entry in "${CERTS[@]}"; do
    IFS=':' read -r cert_file key_file service <<< "$cert_entry"
    check_and_renew "$cert_file" "$key_file" "$service" || true
done
 
log "=== Preverjanje obnove končano ==="
chmod +x /usr/local/bin/cert-renewal.sh

Cron naloga

# /etc/cron.d/cert-renewal
# Dnevno ob 03:00
0 3 * * * root /usr/local/bin/cert-renewal.sh

Linux: Systemd Timer

# /etc/systemd/system/cert-renewal.service
[Unit]
Description=Certificate Renewal Service
After=network.target
 
[Service]
Type=oneshot
ExecStart=/usr/local/bin/cert-renewal.sh
StandardOutput=journal
StandardError=journal
 
[Install]
WantedBy=multi-user.target
# /etc/systemd/system/cert-renewal.timer
[Unit]
Description=Daily Certificate Renewal Check
 
[Timer]
OnCalendar=*-*-* 03:00:00
RandomizedDelaySec=1h
Persistent=true
 
[Install]
WantedBy=timers.target
# Aktivacija timerja
systemctl daemon-reload
systemctl enable --now cert-renewal.timer
 
# Preverjanje statusa
systemctl list-timers cert-renewal.timer
journalctl -u cert-renewal.service

Windows: Task Scheduler + PowerShell

# %SCRIPTS_PATH%\Cert-Renewal.ps1
 
param(
    [int]$RenewalDays = 30,
    [string]$LogPath = "C:\Logs\cert-renewal.log",
    [string]$SmtpServer = "smtp.example.com",
    [string]$MailTo = "pki-team@example.com"
)
 
function Write-Log {
    param([string]$Message)
    $timestamp = Get-Date -Format "yyyy-MM-ddTHH:mm:ss"
    "$timestamp $Message" | Out-File -Append -FilePath $LogPath
}
 
function Test-CertificateExpiry {
    param(
        [string]$CertPath,
        [int]$DaysThreshold
    )
 
    $cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($CertPath)
    $expiryDate = $cert.NotAfter
    $daysUntilExpiry = ($expiryDate - (Get-Date)).Days
 
    return @{
        DaysRemaining = $daysUntilExpiry
        ExpiryDate = $expiryDate
        NeedsRenewal = $daysUntilExpiry -lt $DaysThreshold
        Subject = $cert.Subject
    }
}
 
function Invoke-CertificateRenewal {
    param(
        [string]$CertPath,
        [string]$KeyPath,
        [string]$ServiceName
    )
 
    Write-Log "INFO: Začetek obnove za $CertPath"
 
    try {
        # Ustvarjanje CSR iz obstoječega certifikata
        $cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($CertPath)
 
        # Tukaj bi se izvedel klic CA API
        # Primer: REST klic na notranji CA
        $body = @{
            subject = $cert.Subject
            san = $cert.Extensions | Where-Object { $_.Oid.Value -eq "2.5.29.17" }
        } | ConvertTo-Json
 
        $newCert = Invoke-RestMethod -Uri "https://ca.internal.example.com/api/renew" `
            -Method POST `
            -Body $body `
            -Headers @{ Authorization = "Bearer $(Get-Content %SCRIPTS_PATH%\ca-token.txt)" }
 
        # Ustvarjanje varnostne kopije
        $backupPath = "$CertPath.bak.$(Get-Date -Format 'yyyyMMdd')"
        Copy-Item $CertPath $backupPath
 
        # Shranjevanje novega certifikata
        $newCert | Out-File -FilePath $CertPath -Encoding ascii
 
        # Ponovni zagon storitve
        if ($ServiceName) {
            Restart-Service $ServiceName -Force
            Write-Log "INFO: Storitev $ServiceName ponovno zagnana"
        }
 
        Write-Log "INFO: Obnova uspešna za $CertPath"
 
        # E-poštno obvestilo
        Send-MailMessage -To $MailTo -From "pki@example.com" `
            -Subject "Certifikat obnovljen: $($cert.Subject)" `
            -Body "Certifikat je bil uspešno obnovljen." `
            -SmtpServer $SmtpServer
 
        return $true
    }
    catch {
        Write-Log "ERROR: Obnova neuspešna - $_"
 
        Send-MailMessage -To $MailTo -From "pki@example.com" `
            -Subject "NAPAKA: Obnova certifikata" `
            -Body "Napaka pri obnovi $CertPath : $_" `
            -SmtpServer $SmtpServer
 
        return $false
    }
}
 
# Glavna logika
Write-Log "=== Preverjanje obnove začeto ==="
 
$certificates = @(
    @{ Cert = "C:\certs\webserver.pem"; Key = "C:\certs\webserver.key"; Service = "W3SVC" },
    @{ Cert = "C:\certs\api.pem"; Key = "C:\certs\api.key"; Service = "APIService" }
)
 
foreach ($certConfig in $certificates) {
    $status = Test-CertificateExpiry -CertPath $certConfig.Cert -DaysThreshold $RenewalDays
 
    Write-Log "INFO: $($certConfig.Cert) - $($status.DaysRemaining) dni preostalo"
 
    if ($status.NeedsRenewal) {
        Invoke-CertificateRenewal `
            -CertPath $certConfig.Cert `
            -KeyPath $certConfig.Key `
            -ServiceName $certConfig.Service
    }
}
 
Write-Log "=== Preverjanje obnove končano ==="

Nastavitev Task Scheduler:

# Ustvarjanje naloge
$action = New-ScheduledTaskAction -Execute "PowerShell.exe" `
    -Argument "-ExecutionPolicy Bypass -File %SCRIPTS_PATH%\Cert-Renewal.ps1"
 
$trigger = New-ScheduledTaskTrigger -Daily -At "03:00"
 
$principal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -LogonType ServiceAccount
 
$settings = New-ScheduledTaskSettingsSet -StartWhenAvailable -DontStopOnIdleEnd
 
Register-ScheduledTask -TaskName "Certificate Renewal" `
    -Action $action `
    -Trigger $trigger `
    -Principal $principal `
    -Settings $settings

Ansible avtomatizacija

# cert-renewal.yml
---
- name: Certificate Renewal
  hosts: all
  become: yes
  vars:
    renewal_days: 30
    ca_server: "https://ca.internal.example.com"

  tasks:
    - name: Check certificate expiry
      community.crypto.x509_certificate_info:
        path: "{{ item.cert }}"
      register: cert_info
      loop: "{{ certificates }}"

    - name: Create CSR for expiring certificates
      community.crypto.openssl_csr:
        path: "/tmp/{{ item.item.name }}.csr"
        privatekey_path: "{{ item.item.key }}"
        common_name: "{{ item.subject.commonName }}"
        subject_alt_name: "{{ item.subject_alt_name | default(omit) }}"
      when: (item.not_after | to_datetime('%Y%m%d%H%M%SZ') - ansible_date_time.iso8601 | to_datetime('%Y-%m-%dT%H:%M:%SZ')).days < renewal_days
      loop: "{{ cert_info.results }}"

    - name: Request new certificate from CA
      uri:
        url: "{{ ca_server }}/api/sign"
        method: POST
        body_format: raw
        body: "{{ lookup('file', '/tmp/' + item.item.name + '.csr') }}"
        headers:
          Authorization: "Bearer {{ ca_token }}"
        return_content: yes
      register: new_cert
      when: (item.not_after | to_datetime('%Y%m%d%H%M%SZ') - ansible_date_time.iso8601 | to_datetime('%Y-%m-%dT%H:%M:%SZ')).days < renewal_days
      loop: "{{ cert_info.results }}"

    - name: Deploy new certificate
      copy:
        content: "{{ item.content }}"
        dest: "{{ item.item.item.cert }}"
        backup: yes
      notify: Reload services
      when: item.content is defined
      loop: "{{ new_cert.results }}"

  handlers:
    - name: Reload services
      service:
        name: "{{ item.item.item.service }}"
        state: reloaded
      loop: "{{ new_cert.results }}"
      when: item.changed
# Inventory (group_vars/all.yml)
certificates:
  - name: webserver
    cert: /etc/ssl/certs/webserver.pem
    key: /etc/ssl/private/webserver.key
    service: nginx
  - name: api
    cert: /etc/ssl/certs/api.pem
    key: /etc/ssl/private/api.key
    service: api-service

Integracija nadzora

# Prometheus Node Exporter Textfile
# /var/lib/node_exporter/textfile_collector/cert_expiry.prom
 
for cert in /etc/ssl/certs/*.pem; do
    expiry=$(openssl x509 -enddate -noout -in "$cert" 2>/dev/null | cut -d= -f2)
    if [ -n "$expiry" ]; then
        expiry_epoch=$(date -d "$expiry" +%s)
        name=$(basename "$cert" .pem)
        echo "cert_expiry_seconds{cert=\"$name\"} $expiry_epoch"
    fi
done

Kontrolni seznam

# Kontrolna točka
—————–
1 Skripta za obnovo ustvarjena in preizkušena
2 Cron/Timer/Task konfiguriran
3 CA avtentikacija nastavljena
4 Varnostna kopija pred obnovo
5 Ponovni zagon storitve deluje
6 Obvestilo ob napaki
7 Beleženje aktivirano

Povezana dokumentacija


« ← Kubernetes Cert-Manager | → Scenariji za operaterje »


Wolfgang van der Stille @ EMSR DATA d.o.o. - Post-Quantum Cryptography Professional

Zuletzt geändert: dne 30.01.2026 ob 07:21