goauthentik / authentik

The authentication glue you need.
https://goauthentik.io
Other
13.76k stars 926 forks source link

Renewal of expired certificate #7242

Open depuits opened 1 year ago

depuits commented 1 year ago

Describe your question/ The default authentik Self-signed Certificate has expired and the one I've created to use with ldap will expire soon. The documentation doesn't mention what the correct way to renew this is. Should these be deleted and then be replaced by newly generated ones or is there a way to renew these?

Relevant infos i.e. Version of other software you're using, specifics of your setup

Screenshots If applicable, add screenshots to help explain your problem. image

Logs Output of docker-compose logs or kubectl logs respectively

Version and Deployment (please complete the following information):

Additional context Add any other context about the problem here.

obsidiangroup commented 1 year ago

There should be a way to just regenerate the existing certificate, instead of having to delete the certificate then re-create the certificate, and re-associate it to everything. Since I assume in the schema each certificate has a primary key that everything else just points to the PK, this would seem the easiest.

fansari commented 11 months ago

We have the same issue with our letsenrypt certificate. How can a renewal done automatically? Delete, recreate and the reassociate to all providers (we have quite a lot) cannot be the idea.

bdomars commented 7 months ago

Just hit the same snag with our SAML providers, quite a lot of work to go throug everything and update a certificate. Seems like there should be a better way to do this automatically.

angelnu commented 6 months ago

This also affects external certificates .

The certificate in my Kubernetes cluster was correctly updated by cert-manager and the new certificate files were mounted to authentik in the /certs folder. Authentik was still using the old (expired) certificate.

After I deleted the certificate in the authentik UI and restarted authentik, the certificate was re-imported again. So it seems that authentik does not check if the certificate has changed in the filesystem - it keeps using the old values until manually deleted.

los93sol commented 6 months ago

There is a certificate discovery task under System Tasks that can be run, but best I can tell it's completely broken (at least for k8s setups) and the workaround is to use the import_certificate command.

import_certificate does work, but that means I've got to manually mount the files because Certificate secrets mounted are not named in a way that is compatible with Authentik and then I also have to have a cron running to keep them up to date within the container. Imagine a deployment at any kind of scale would be extremely hard to manage and the current state of this issue is creating a ton of unnecessary work for users.

@BeryJu Is there any plan to address this so that at least the certificate discovery task can function as intended? Even that would be a huge step forward from having to make a cron around import_certificate. This seems like a feature that should work out of the box

Toakan commented 5 months ago

So the biggest problem here is that whilst you can overwrite the local /certs/domainname/ with the newer chains, the Import isn't setup to overwrite the current data in DB.

IE Below is a failure reported by the certificate_discovery task. It looks like the job should be changed to a IF EXISTS check before INSERT, otherwise UPDATE.

File "/ak-root/venv/lib/python3.12/site-packages/django/db/models/base.py", line 1067, in _save_table results = self._do_insert( ^^^^^^^^^^^^^^^^ File "/ak-root/venv/lib/python3.12/site-packages/django/db/models/base.py", line 1108, in _do_insert return manager._insert( ^^^^^^^^^^^^^^^^ File "/ak-root/venv/lib/python3.12/site-packages/django/db/models/manager.py", line 87, in manager_method return getattr(self.get_queryset(), name)(*args, **kwargs)

File "/ak-root/venv/lib/python3.12/site-packages/django_prometheus/db/common.py", line 69, in execute return super().execute(*args, **kwargs) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/ak-root/venv/lib/python3.12/site-packages/psycopg/cursor.py", line 732, in execute raise ex.with_traceback(None) django.db.utils.IntegrityError: duplicate key value violates unique constraint "authentik_crypto_certificatekeypair_name_719603a4_uniq" DETAIL: Key (name)=(DOMAIN.CO.UK) already exists.
Toakan commented 5 months ago

@BeryJu Can this issue be changed to BUG tag?

It breaks automatic deployments, and induces unnecessary downtime having to rebuild the Providers using Certificates.

tobiaszuercher commented 3 months ago

what is the current designed idea in how to renew this certificate?

as a quickfix: could we have the option for a longer valid self signed certificate until there is a better solution to rotate?

jejbq commented 1 month ago

You could use the following doc until a renew option is available

renew_cert.py:

#!/usr/bin/env python3
#
# pip3 install -U --user --break-system-packages cryptography
#
import os
from cryptography import x509
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization, hashes
from cryptography.hazmat.primitives.asymmetric import rsa
from datetime import datetime, timedelta

def load_certificate(cert_path):
    with open(cert_path, "rb") as cert_file:
        cert_data = cert_file.read()
    return x509.load_pem_x509_certificate(cert_data, default_backend())

def renew_certificate(cert, private_key, days=3650):
    # Create a new certificate builder
    builder = x509.CertificateBuilder()

    # Copy attributes from the existing certificate
    builder = builder.subject_name(cert.subject)
    builder = builder.issuer_name(cert.issuer)
    builder = builder.not_valid_before(cert.not_valid_before_utc)
    builder = builder.not_valid_after(cert.not_valid_before_utc + timedelta(days=days))
    builder = builder.serial_number(cert.serial_number)
    builder = builder.public_key(cert.public_key())

    # Add extensions
    for ext in cert.extensions:
        builder = builder.add_extension(ext.value, ext.critical)

    # Sign the new certificate with the existing private key
    new_cert = builder.sign(private_key, hashes.SHA256(), default_backend())
    return new_cert

def save_certificate(cert, output_path):
    with open(output_path, "wb") as cert_file:
        cert_file.write(cert.public_bytes(encoding=serialization.Encoding.PEM))

def main():
    cert_path = "authentik Self-signed Certificate_certificate.pem"  # Path to your existing certificate
    private_key_path = "authentik Self-signed Certificate_private_key.pem"    # Path to your existing private key
    output_cert_path = "authentik Self-signed Certificate_certificate_new.pem" # Path to save the new certificate

    # Load existing certificate and private key
    cert = load_certificate(cert_path)
    with open(private_key_path, "rb") as key_file:
        private_key = serialization.load_pem_private_key(key_file.read(), password=None, backend=default_backend())

    # Renew the certificate
    new_cert = renew_certificate(cert, private_key)

    # Save the new certificate
    save_certificate(new_cert, output_cert_path)
    print(f"New certificate saved to {output_cert_path}")

if __name__ == "__main__":
    main()
Go to https://authentik.example.com/if/admin/#/crypto/certificates
Expand "authentik Self-signed Certificate"
Click on "Download Certificate"
Click on "Download Private key"

mv ~/Downloads/authentik\ Self-signed\ Certificate_*.pem .
pip3 install -U --user --break-system-packages cryptography
cat <<'EOF'> renew_cert.py
...
--- 8< --- paste the renew_cert.py code here --- 8< ---
...
EOF
chmod +x renew_cert.py
./renew_cert.py 
New certificate saved to authentik Self-signed Certificate_certificate_new.pem

diff -Naur \
<(openssl x509 -in 'authentik Self-signed Certificate_certificate.pem' -noout -text) \
<(openssl x509 -in 'authentik Self-signed Certificate_certificate_new.pem' -noout -text)

Go to https://authentik.example.com/if/admin/#/crypto/certificates
Edit (Actions) "authentik Self-signed Certificate"
Rename "authentik Self-signed Certificate" to "authentik Self-signed Certificate [OLD]"
Click on "Update"
Click on "Create"
Type "authentik Self-signed Certificate" in "Name"
Paste the content of 'authentik Self-signed Certificate_certificate_new.pem' in "Certificate"
Paste the content of 'authentik Self-signed Certificate_private_key.pem' in "Private Key"
Click on "Create"

Go to https://authentik.example.com/if/admin/#/core/providers
Edit (Actions) every single line with "Type" equal to "OAuth2/OpenID Provider" or "SAML Provider"
- For OAuth2/OpenID Provider: Change the "Signing Key" in Protocol settings to "authentik Self-signed Certificate"
- For SAML Provider: Change the "Signing Certificate" in "Advanced protocol settings" to "authentik Self-signed Certificate"
Click on "Update"

Add the new certificate to all your Apps...

N.B. Before doing all this, I tried to renew it via Authentik API but is not easy via the shell to import authentik and keep the same private key. https://raw.githubusercontent.com/goauthentik/authentik/refs/heads/main/authentik/core/tests/utils.py https://raw.githubusercontent.com/goauthentik/authentik/refs/heads/main/authentik/crypto/apps.py https://raw.githubusercontent.com/goauthentik/authentik/refs/heads/main/authentik/crypto/builder.py