HARICA ondersteunt ACME (Automated Certificate Management Environment), een open standaard voor het automatisch installeren en vernieuwen van servercertificaten (zie RFC 8555 en https://www.acmeisuptime.com/). Zeker gezien het geplande inkorten van de levensduur van certificaten verwachten we dat ACME op termijn de standaardmanier van certificaataanvragen wordt.

In September 2025 gaven we hier weer een webinar over, hier onder door te klikken. Alle informatie uit het webinar staat ook op deze wiki:

ACME is oorspronkelijk ontwikkeld door de Internet Security Research Group (ISRG), de partij achter Let's Encrypt. Veel ACME clients, documentatie, en zelfs de directories die certbot aanmaakt bevatten verwijzingen naar Let's Encrypt. Desondanks gebruikt ACME via HARICA natuurlijk alleen certificaten van HARICA.

ACME met External Account Binding (EAB)

Om ACME met HARICA te kunnen gebruiken, moeten in de interface op https://cm.harica.gr zogenaamde ACME EAB-accounts worden aangemaakt. Deze zorgen voor een koppeling tussen de ACME client en de instelling, waardoor certificaten ook weer via de interface te zien zijn. Er zijn bij HARICA drie smaken ACME:

NaamStatusSoort CertificatenValidatiesScope
Enterprise AdminStandaardTLS OV of DVin plaats van ACME-challenges worden de validaties in CertManager (in de lijst met domeinen) gebruikt. Bij DV is een fallback naar een ACME-challenge (http of dns)(sub)domeinen zowel met include als exclude configureerbaar in CertManager
Enterprise User (Eindgebruikers)Handmatig in te schakelenTLS DVgebruiker moet altijd een ACME-challenge (http of dns) voor domeinvalidatie doenalle domeinen binnen de Enterprise
Enterprise GroupToekomstige FunctionaliteitTLS OVnog niet volledig uitgewerkt(sub)domeinen beperkt door toewijzing aan specifieke groepen in CertManager, de groep beheert de eigen validaties

In álle gevallen moet een domein al aan de Enterprise zijn toegevoegd. Daarnaast is het opgeven van een mailadres in de ACME transactie altijd verplicht, hoewel dit adres niks te maken hoeft te hebben met het account.

Bij alle vormen van EAB is er geen direct verband tussen een specifieke gebruiker en de bruikbaarheid van de EAB. Iedereen die over de EAB-gegevens beschikt, kan certificaten uitgeven. Dit betekent dat zelfs als een gebruiker wordt gearchiveerd, de EAB sleutels geldig blijven totdat een beheerder ze expliciet uitschakelt. Er worden geen certificaten ingetrokken, tenzij een beheerder die actie onderneemt. In alle gevallen heeft een Enterprise Admin de verantwoordelijkheid en bevoegdheid om dergelijke beslissingen namens de organisatie te nemen. Bovendien kan de beheerder de gearchiveerde gebruiker zien in het veld Created By (Aangemaakt door) van de ACME-gegevens. Indien nodig kan die auditlogboeken voor die specifieke gebruiker opvragen bij HARICA.

Domeinvalidatie

Om certificaten uit te geven zonder DCV te hoeven doen tijdens de ACME transactie is het nog steeds nodig om een domein eerst handmatig (met een txt record) te valideren in CertManager. Omdat ook de tijd dat een dergelijke validatie als geldig mag worden beschouwt korter zal worden, zet HARICA voor de toekomst in op twee alternatieven: het verwerken van ACME validaties in CertManager en CA-assisted domain validation. Dat eerste houdt in dat als je DCV doet tijdens een ACME transactie (met http of dns validatie) de geldigheidsduur van de validatie in CertManager ook wordt geupdate. Het tweede houd in dat je door middel van een CNAME op je domein een permanente link naar HARICA creëert, zodat zij op elk moment dat dat nodig zou zijn het domein kunnen valideren. Beide functies worden in 2026, ruim voor het inkorten van de geldigheidsduur van validaties, verwacht. 

ACME voor Enterprise Admins

Dit is de standaard implementatie van ACME, die qua functionaliteit overeenkomt met wat we gewend waren bij de vorige leverancier: Enterprise Admins maken EAB-credentials aan die voor specifieke domeinen gebruikt kunnen worden. Het is hierbij mogelijk om in je ACME client domeinvalidatie over te slaan.

  • Ga naar "Enterprise" → "Admin" en selecteer dan het tabblad "ACME" bovenaan:
  • Hier kan vervolgens het beheer worden uitgevoerd. Er kunnen accounts gemaakt worden met "Create+". De friendly name is hier bedoeld als identificatie in de lijst met accounts:

  • Als het account is aangemaakt moet je de scope van domeinen bepalen. Selecteer hiervoor het account en ga naar het tabblad "Domains":
  • Gebruik hierna de EAB credentials onder "Details" in je favoriete ACME client of communiceer ze via een veilig kanaal naar de beheerder die er mee aan de slag gaat.

ACME voor Eindgebruikers

Dit is een extra implementatie van ACME, die qua functionaliteit overeenkomt met Let's Encrypt: eindgebruikers krijgen (met een persoonlijke HMAC key) toegang tot een ACME-server waarop ze certificaten kunnen aanvragen, zo lang ze maar DCV kunnen doen tijdens de ACME transactie en het domein toegevoegd is aan de enterprise in Cert Manager.

  • Inschakelen gebeurt eenmalig door een Enterprise Admin via Enterprise → Admin → Enterprise selecteren → Op de organisatie onder "Legal Name" te klikken → rechtsboven op het knopje "tags" te drukken (plaatje van een label). Daar kan dan de schakelaar voor #ACME-Personal aangezet worden:


  • Hiermee wordt voor alle gebruikers in het linker menu onder "eSign Documents" een nieuwe knop ACME beschikbaar waarmee ACME accounts kunnen worden beheerd.


  • Bij het gebruik van Personal ACME moet er wel voor ieder certificaat een DNS-01 of HTTP-01 challenge gedaan worden en de HMAC key worden opgegeven. 

Implementatie

Voor het gebruik van ACME installeer je een client op de machine die het certificaat nodig heeft (meestal de webserver). HARICA geeft endpoints voor de server. De meestgebruikte ACME-client is certbot, maar ook andere clients die zich houden aan de standaard werken. We noemen een aantal implementaties onderaan deze pagina. Certbot wordt typisch gebruikt op UNIX systemen, maar er zijn ook alternatieven voor Windows. Zie ook https://letsencrypt.org/docs/client-options/. 

De volledige beschrijving staat in de op https://guides.harica.gr/docs/Guides/Server-Certificate/ACME-Instructions/, maar hieronder geven we een voorbeeld met certbot. 

Gebruik van certbot met HARICA ACME Service
# install certbot
# See instructions at https://certbot.eff.org

# Register using ID and key for external account binding:
sudo certbot register \
--server https://acme-v02.harica.gr/acme/1f3....6-e0e7-474a-....-2275....38ed/directory \
--eab-kid 8QBpgR....s5vOgsFtbk \
--eab-hmac-key HFi8ey....foqY_V0yMP...8566XwczX....LZqedJY \
--email certificaten-beheer@surf.nl --agree-tos --no-eff-email

# Issue certificate:
sudo certbot certonly --standalone --domain acme-demo.surfcertificaten.nl --server https://acme-v02.harica.gr/acme/1f3....6-e0e7-474a-....-2275....38ed/directory

Overige parameters

  • Je krijgt standaard een ECC certificaat sinds certbot 2.0, dit kun je aanpassen door een "--key-type" mee te geven. 
  • Een email en agree-tos is altijd vereist voor HARICA en de meeste publieke CA's vanwege de acceptatie van de Subscriber Agreement en om contact op te nemen  in geval van revocation, zoals beschreven in de HARICA CP/CPS.
  • De --server is verplicht, anders default certbot naar Let's Encrypt. Vergeet deze ook bij handmatig revoken of vernieuwen niet.

Bekende foutmeldingen  

Foutmelding "not whitelisted"  

De volgende foutmelding betekent: het domein is niet opgeslagen in het Enterprise EAB-account, maar is in principe beschikbaar in Enterprise.

  • Controleer het aangevraagde domein. Is er misschien een typfout?
  • Configureer het Enterprise EAB-account: maak in het tabblad "Domeinen" van het account een toegangsregel aan voor het gewenste domein.

Foutmelding "Identifiers could not be parsed"  

Dat betekent dat het domein is niet beschikbaar in de enterprise. 

  • Controleer het aangevraagde domein. Is er misschien een typfout?
  • Voeg het domein toe aan de enterprise onder Enterprise → Admin → Domains en valideerd deze

Specifieke appliances

Zie https://github.com/SURFnet/acme-capability-matrix. Dat overizcht is op basis van informatie die wij krijgen van gebruikers en dus geen expliciete aanbevelingen vanuit SURFcertificaten. Heb je goede ervaring of voorbeeldimplementaties van een andere tool? Laat het ons weten! Do een pull request of mail certificaten-beheer@surf.nl.

  • Ansible kan met acme_certifcate certificaten ophalen. Een goede start is onderstaande code, een uitgebreider voorbeeld is beschikbaar via https://edu.nl/r63jq. Bij de community.crypto -modules van Ansible moet de parameter modify_account: false worden opgegeven om het aanmaken van certificaten te laten werken.
    playbook.yaml
    ---
    - hosts: localhost
      tasks:
        - name: Set variables
          set_fact:
            acme_account_dir: /tmp/ssl/acme/account   # Directory for the ACME account key
            cert_dir: /tmp/ssl/certs/acme             # Directory for per-domain cert files
    
            acme_directory_url: "https://acme-v02.harica.gr/acme/<uuid>/directory" # Replace with your acme directory url (from internal support)
            acme_accountkey_name: SURF-teamname                                    # name for account key (eg your SURF-teamname)
            acme_contact_email: team-mail@surf.nl                                  # Your team e-mail address 
            acme_eab_kid: "<key-id>"                                               # Received from internal support
            acme_eab_key: "<key>                                                   # Received from internal support
            acme_eab_alg: "HS256"
            acme_key_type: RSA
            acme_rsa_bits: 4096
            acme_remaining_days: 30     # Trigger renewal when <= 30 days remain (safe margin for weekly cron)
    
            csr_country_name: "NL"
            csr_locality_name: "Utrecht"
            csr_organization_name: "SURF Teamname"
            csr_email_address: "team-mail@surf.nl"
    
            fqdn: my_fqdn.surf.net                                                 # FQDN for certificate
            alternative_names: "DNS:my_fqdn2.surf.net,DNS:my_fqdn3.surf.net"       # Alternative names in certificate
    
            slack_webhook_url: "https://hooks.slack.com/services/YOUR/WEBHOOK/URL" # Slack webhook URL for channel
            slack_channel: "#alerts"                                               # Slack channel
    
        - block:
            # 1. Check if the ACME account key already exists
            - name: Check if ACME account key exists
              ansible.builtin.stat:
                path: "{{ acme_account_dir }}/{{ acme_accountkey_name }}.pem"
              register: account_key_file
    
            # 2. Generate a new account key only if it doesn't exist yet
            - name: Generate ACME account key
              community.crypto.openssl_privatekey:
                path: "{{ acme_account_dir }}/{{ acme_accountkey_name }}.pem"
                type: RSA
                size: 4096
                force: false
              when: not account_key_file.stat.exists
    
            # 3. Register with ACME via EAB — only runs once when the key is brand new
            - name: Register ACME account with EAB
              community.crypto.acme_account:
                account_key_src: "{{ acme_account_dir }}/{{ acme_accountkey_name }}.pem"
                acme_directory: "{{ acme_directory_url }}"
                acme_version: "2"
                state: present
                allow_creation: true
                terms_agreed: true
                contact:
                  - "mailto:{{ acme_contact_email }}"
                external_account_binding:
                  alg: "{{ acme_eab_alg }}"
                  key: "{{ acme_eab_key }}"
                  kid: "{{ acme_eab_kid }}"
              when: not account_key_file.stat.exists
              run_once: true
    
            # 4. Generate the private key for the certificate (force: false prevents regeneration on every run)
            - name: Generate certificate private key
              community.crypto.openssl_privatekey:
                path: "{{ cert_dir }}/{{ fqdn }}.key"
                type: RSA
                size: 4096
                format: pkcs8
                force: false
    
            # 5. Generate the Certificate Signing Request (CSR)
            - name: Generate certificate CSR
              community.crypto.openssl_csr:
                path: "{{ cert_dir }}/{{ fqdn }}.csr"
                privatekey_path: "{{ cert_dir }}/{{ fqdn }}.key"
                common_name: "{{ fqdn }}"
                country_name: "{{ csr_country_name }}"
                locality_name: "{{ csr_locality_name }}"
                organization_name: "{{ csr_organization_name }}"
                email_address: "{{ csr_email_address }}"
                subject_alt_name: "{{ alternative_names }}"
                state: present
                use_common_name_for_san: false
                key_usage:
                  - digitalSignature
                  - keyAgreement
                extended_key_usage:
                  - clientAuth
                  - serverAuth
              register: csr_result
    
            # 6. Initiate the ACME order
            - name: Initiate certificate order
              community.crypto.acme_certificate:
                acme_version: "2"
                acme_directory: "{{ acme_directory_url }}"
                account_key_src: "{{ acme_account_dir }}/{{ acme_accountkey_name }}.pem"
                csr: "{{ cert_dir }}/{{ fqdn }}.csr"
                fullchain_dest: "{{ cert_dir }}/{{ fqdn }}.crt"
                challenge: "no challenge"
                remaining_days: "{{ acme_remaining_days }}"
                modify_account: false
                force: "{{ csr_result.changed }}"
              register: get_certificate_info
    
            # 7. Finalize and download the certificate
            - name: Finalize and retrieve certificate
              community.crypto.acme_certificate:
                acme_version: "2"
                acme_directory: "{{ acme_directory_url }}"
                account_key_src: "{{ acme_account_dir }}/{{ acme_accountkey_name }}.pem"
                csr: "{{ cert_dir }}/{{ fqdn }}.csr"
                fullchain_dest: "{{ cert_dir }}/{{ fqdn }}.crt"
                challenge: "no challenge"
                remaining_days: "{{ acme_remaining_days }}"
                modify_account: false
                force: "{{ csr_result.changed }}"
                data: "{{ get_certificate_info }}"
              when: get_certificate_info is changed
              register: finalize_result
    
            # 8. Notify Slack on successful renewal
            - name: Notify Slack on certificate renewal
              community.general.slack:
                token: "{{ slack_webhook_url }}"
                msg: >-
                  :white_check_mark: Certificate renewed for *{{ fqdn }}*.
                  New certificate written to `{{ cert_dir }}/{{ fqdn }}.crt`.
                channel: "{{ slack_channel }}"
              when: get_certificate_info is changed
    
          rescue:
            # Runs if ANY task in the block above fails
            - name: Notify Slack on failure
              community.general.slack:
                token: "{{ slack_webhook_url }}"
                msg: >-
                  :x: ACME certificate renewal failed for *{{ fqdn }}*.
                  Failed task: *{{ ansible_failed_task.name }}*
                  Error: {{ ansible_failed_result.msg }}
                channel: "{{ slack_channel }}"
    
            # Re-raise the error so the playbook still exits non-zero (important for cron monitoring)
            - name: Re-raise failure
              ansible.builtin.fail:
                msg: "Playbook failed — see Slack for details. Original error: {{ ansible_failed_result.msg }}"
  • Cert-manager.io wordt veel gebruikt voor kubernetes. Hieronder een voorbeeldconfiguratie waarbij annotations: cert-manager.io/issuer: cert-issuer gebruikt wordt op de ingress:
    issuer.yaml
    apiVersion: cert-manager.io/v1
    kind: Issuer
    spec:
      acme:
        email: ... <-- vrij in te vullen, wel verplicht
        externalAccountBinding:
          keyID: ... <-- Key ID uit CertManager
          keySecretRef:
            key: secret
            name: harica-eab-secret-automation <-- aanmaken op basis van de HMAC Key uit CertManager
        privateKeySecretRef:
          name: ...  <- zelf aanmmaken en invullen
        server: ... <- de Server URL uit CertManager
    secret
    apiVersion: v1
    data:
      secret: "<path:...-base64encoded>"
    kind: Secret
    metadata:
      name: harica-eab-secret-automation
      namespace: ... <-- naar eigen inzicht
    type: Opaque

Checks bij problemen

Als er een fout optreedt, kan je eerst de volgende punten controleren:

Enterprise ACME-accounts:

  1. Zijn de gegevens Key-ID, HMAC-Key, Server-URL correct opgegeven in de oproep van de ACME-client?
  2. Is de FQDN () waarvoor een certificaat wordt aangevraagd, daadwerkelijk geactiveerd en beschikbaar in het ACME-account? Heeft u expliciete regels toegevoegd in het tabblad 'Domeinen'?
  3. Is het domein succesvol gevalideerd in Enterprise?
  4. Is er een parameter –email doorgegeven aan de ACME-client? 

Persoonlijke ACME-accounts

  1. Zijn de gegevens Key-ID, HMAC-Key, Server-URL correct opgegeven in de oproep van de ACME-client?
  2. Staat het domein vermeld in Enterprise (validatie in CertManager niet nodig)?
  3. Is er een parameter –email doorgegeven aan de ACME-client?
  4. Kan de ACME-client een HTTP- of DNS-challenge beantwoorden? Zijn er hierbij mogelijk fouten opgetreden (controleer dit in het logboek van de ACME-client)?
  • No labels