Howto install PrivacyIDEA 3.10 on Debian 12 with Freeradius 3.0

Create VM. 5GB disk, 1GB RAM, 1 CPU. Install Debian 12 from netinst ISO. Choose Software: Standard System Utilties, SSH Server (default)

Install apache2, freeradius, radius plugin dependencies, MariaDB and dev stuff, python venv and dev stuff, pgk-config:
apt install freeradius freeradius-utils python3 python3-venv virtualenv python3-virtualenv python3-wheel python3-all-dev apache2 pkg-config mariadb-server mariadb-client libmariadb-dev-compat libmariadb-dev libapache2-mod-wsgi-py3 gcc libconfig-inifiles-perl libdata-dump-perl libtry-tiny-perl libjson-perl liblwp-protocol-https-perl liburi-encode-perl

Add User and config directory:

adduser --system --group --home /opt/privacyidea/ privacyidea
mkdir  /etc/privacyidea
chown privacyidea:privacyidea /etc/privacyidea

Configure MariaDB safely:
mysql_secure_installation
-Switch to unix_socket authentication [Y/n] choose NO!
-Choose defaults for remaining questions

Create Database and User:
mysql -u root -p

CREATE DATABASE pi;
CREATE USER "pi"@"localhost" IDENTIFIED BY "<pidbuserpassword>";
GRANT ALL PRIVILEGES ON pi.* TO "pi"@"localhost";
flush privileges;
exit

Create /etc/privacyidea/pi.cfg with following content:

import logging

# The realm, where users are allowed to login as administrators
SUPERUSER_REALM = ['super', 'administrators']

# Your database
SQLALCHEMY_DATABASE_URI = 'mysql://pi:<pidbuserpassword>@localhost/pi'

# This is used to encrypt the token data and token passwords
PI_ENCFILE = '/etc/privacyidea/enckey'

# This is used to sign the audit log
PI_AUDIT_KEY_PRIVATE = '/etc/privacyidea/private.pem'
PI_AUDIT_KEY_PUBLIC = '/etc/privacyidea/public.pem'

# Truncate Audit entries to fit into DB columns
PI_AUDIT_SQL_TRUNCATE = True

# The Class for managing the SQL connection pool
PI_ENGINE_REGISTRY_CLASS = "shared"
PI_AUDIT_POOL_SIZE = 20

# Logging options
PI_LOGFILE = '/var/log/privacyidea/privacyidea.log'
PI_LOGLEVEL = logging.INFO

# Set maximum identifier length to 128
# SQLALCHEMY_ENGINE_OPTIONS = {"max_identifier_length": 128}

# PI_AUDIT_MODULE = <python audit module>
# PI_AUDIT_SQL_URI = <special audit log DB uri>

# Options passed to the Audit DB engine (supersedes SQLALCHEMY_ENGINE_OPTIONS)
# PI_AUDIT_SQL_OPTIONS = {}
# PI_INIT_CHECK_HOOK = 'your.module.function'
# PI_UI_DEACTIVATED = True
# PI_CSS = '/location/of/theme.css'

Generate secrets and add them to pi.cfg:

PEPPER="$(tr -dc A-Za-z0-9_ </dev/urandom | head -c24)"
echo "PI_PEPPER = '$PEPPER'" >> /etc/privacyidea/pi.cfg
SECRET="$(tr -dc A-Za-z0-9_ </dev/urandom | head -c24)"
echo "SECRET_KEY = '$SECRET'" >> /etc/privacyidea/pi.cfg

Create logfile and directory, change owner to privacyidea:

mkdir /var/log/privacyidea
chown privacyidea:privacyidea /var/log/privacyidea
touch /var/log/privacyidea/privacyidea.log
chown privacyidea:privacyidea /var/log/privacyidea/privacyidea.log

Add Apache config with following content:

touch /etc/apache2/sites-available/privacyidea.conf
ln -s /etc/apache2/sites-available/privacyidea.conf /etc/apache2/sites-enabled/privacyidea.conf
WSGIPythonHome /opt/privacyidea
<VirtualHost _default_:443>
	ServerAdmin webmaster@localhost
	# You might want to change this
	ServerName yourhostname
        DocumentRoot "/var/www/"
	
	<Directory />
		# For Apache 2.4 you need to set this:
		Require all granted
		Options FollowSymLinks
		AllowOverride None
	</Directory>

        # Yubico servers use /wsapi/2.0/verify as the path in the
        # validation URL. Some tools (e.g. Kolab 2fa) let the 
        # user/admin change the api host, but not the rest of
        # the URL. Uncomment the following two lines to reroute 
        # the api URL internally to privacyideas /ttype/yubikey.
        #RewriteEngine  on
        #RewriteRule    "^/wsapi/2.0/verify"  "/ttype/yubikey" [PT]

	# We can run several instances on different paths with different configurations
	WSGIScriptAlias /      /opt/privacyidea/etc/privacyidea/privacyideaapp.wsgi
	#WSGIScriptAlias /instance1      /home/privacyidea/deploy/privacyideaapp1.wsgi
	#WSGIScriptAlias /instance2      /home/privacyidea/deploy/privacyideaapp2.wsgi
	#WSGIScriptAlias /instance3      /home/privacyidea/deploy/privacyideaapp3.wsgi
	
	#
	# The daemon is running as user 'privacyidea'
	# This user should have access to the encKey database encryption file
	WSGIDaemonProcess privacyidea processes=1 threads=15 display-name=%{GROUP} user=privacyidea
	WSGIProcessGroup privacyidea
	WSGIPassAuthorization On

	ErrorLog /var/log/apache2/error.log

	LogLevel warn
	# Do not use %q! This will reveal all parameters, including setting PINs and Keys!
	# Using SSL_CLINET_S_DN_CN will show you, which administrator did what task
	LogFormat "%h %l %u %t %>s \"%m %U %H\"  %b \"%{Referer}i\" \"%{User-agent}i\"" privacyIDEA
	CustomLog /var/log/apache2/ssl_access.log privacyIDEA

	#   SSL Engine Switch:
	#   Enable/Disable SSL for this virtual host.
	SSLCertificateFile	/etc/ssl/certs/ssl-cert-snakeoil.pem
	SSLCertificateKeyFile   /etc/ssl/private/ssl-cert-snakeoil.key

	

	<FilesMatch "\.(cgi|shtml|phtml|php)$">
		SSLOptions +StdEnvVars
	</FilesMatch>
	<Directory /usr/lib/cgi-bin>
		SSLOptions +StdEnvVars
	</Directory>
</VirtualHost>

Install freeradius plugin:

mkdir /usr/share/privacyidea
mkdir /usr/share/privacyidea/freeradius
cd /usr/share/privacyidea/freeradius
wget https://raw.githubusercontent.com/privacyidea/FreeRADIUS/refs/heads/master/privacyidea_radius.pm

Create /etc/privacyidea/rlm_perl.ini with following content:

[Default]
URL = https://localhost/validate/check
REALM =
#RESCONF = someResolver
#SSL_CHECK = false
#DEBUG = true
#TIMEOUT = 10

#[Mapping]
#serial = privacyIDEA-Serial
#
#[Mapping user]
#group = Class

Download freeradius privacyidea perl module config and activate:

cd /etc/freeradius/3.0/mods-available/
wget https://raw.githubusercontent.com/privacyidea/FreeRADIUS/refs/heads/master/config/freeradius3/mods-perl-privacyidea
ln -s /etc/freeradius/3.0/mods-available/mods-perl-privacyidea /etc/freeradius/3.0/mods-enabled/mods-perl-privacyidea

Download freeradius privacyidea authentication config and activate:

cd /etc/freeradius/3.0/sites-available/
wget https://raw.githubusercontent.com/privacyidea/FreeRADIUS/refs/heads/master/config/freeradius3/privacyidea
ln -s /etc/freeradius/3.0/sites-available/privacyidea /etc/freeradius/3.0/sites-enabled/privacyidea

delete default freeradius config links:

rm /etc/freeradius/3.0/sites-enabled/default
rm /etc/freeradius/3.0/sites-enabled/inner-tunnel
rm /etc/freeradius/3.0/mods-enabled/eap

Configure Freeradius /etc/freeradius/3.0/clients.conf, add the following:

client kemp {
    ipaddr = 10.10.10.10
    secret = <radiussecret>
}

Restart freeradius:
systemctl restart freeradius

Enable Apache WSGI and SSL modules and restart apache:

a2enmod wsgi
a2enmod ssl
systemctl restart apache2

Create and activate Virtual Python environment, as user “privacyidea”:

su -s /bin/bash privacyidea
python3 -m venv /opt/privacyidea/
cd /opt/privacyidea
source bin/activate

Install PrivacyIdea dependencies and PrivacyIdea:

pip3 install wheel
pip3 install -r https://raw.githubusercontent.com/privacyidea/privacyidea/v3.10/requirements.txt
pip3 install privacyidea==3.10
pip3 install mysqlclient
pip3 cache purge # clear pip download cache to save some diskspace

Install webauthn client js:

mkdir /opt/privacyidea/lib/python3.11/site-packages/privacyidea/static/contrib/js/webauthn-client
cd /opt/privacyidea/lib/python3.11/site-packages/privacyidea/static/contrib/js/webauthn-client
wget https://raw.githubusercontent.com/privacyidea/webauthn-client/refs/heads/master/pi-webauthn.js

Generate keys:

pi-manage create_enckey  # encryption key for the database
pi-manage create_audit_keys  # key for verification of audit log entries

Create DB:

pi-manage createdb
pi-manage db stamp head -d /opt/privacyidea/lib/privacyidea/migrations/

Disable welcome screen:
pi-manage policy create welcome_disable webui hide_welcome_info

Create Admin user:
pi-manage admin add admin

Done, exit virtual environment and privacy user shell
exit