Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 75 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
# SecureEye

[![Tests](https://img.shields.io/github/actions/workflow/status/vhrabar/SecureEye/pytests.yml?style=for-the-badge&label=tests&logo=pytest&logoColor=white)](https://github.com/vhrabar/SecureEye/actions/workflows/pytests.yml)
[![Lint](https://img.shields.io/github/actions/workflow/status/vhrabar/SecureEye/lint.yml?style=for-the-badge&label=lint&logo=ruff&logoColor=white)](https://github.com/vhrabar/SecureEye/actions/workflows/lint.yml)
[![CodeQL](https://img.shields.io/github/actions/workflow/status/vhrabar/SecureEye/CodeQL.yml?style=for-the-badge&label=codeql&logo=github)](https://github.com/vhrabar/SecureEye/actions/workflows/CodeQL.yml)
[![Copr build status](https://copr.fedorainfracloud.org/coprs/vhrabar/SecureEye/package/secure-eye/status_image/last_build.png)](https://copr.fedorainfracloud.org/coprs/vhrabar/SecureEye/package/secure-eye/)

[![Latest release](https://img.shields.io/github/v/release/vhrabar/SecureEye?include_prereleases&sort=semver&style=for-the-badge&logo=github)](https://github.com/vhrabar/SecureEye/releases)
[![Status: Alpha](https://img.shields.io/badge/status-alpha-orange?style=for-the-badge&logo=semver&logoColor=white)](#)
Expand Down Expand Up @@ -53,13 +52,22 @@ These mirror the package `Build-Depends` in `secureEye/debian/control`:

#### Install Dependencies

On **Debian / Ubuntu**:

```bash
sudo apt-get update && sudo apt-get install -y \
meson ninja-build pkg-config build-essential \
python3 python3-pip python3-venv \
libpam0g-dev libinih-dev libevdev-dev
```

On **Arch Linux** (`libinih` provides `INIReader`, `pam` provides the PAM headers):

```bash
sudo pacman -S --needed base-devel meson ninja pkgconf \
python python-pip pam libinih libevdev
```

#### Build

```bash
Expand All @@ -68,13 +76,14 @@ meson compile -C build
```

> [!WARNING]
> Do **not** run `meson install` on a machine where you also use the `.deb`
> packages. Meson's default prefix is `/usr/local`, and `/usr/local/lib/...`
> shadows the packaged `/usr/lib/...` systemd unit (and `/usr/local/bin` shadows
> `/usr/bin`), which breaks the daemon and CLI. A bare `meson install` also does
> **not** create the recognition virtualenv, that is built by the
> `secureeye-authd` package at install time, so the daemon will not start.
> For a working system install, build, and install the Debian packages below.
> Do **not** run `meson install` on a machine where you also use the packaged
> builds (`.deb`, `.rpm` or the AUR package). Meson's default prefix is
> `/usr/local`, and `/usr/local/lib/...` shadows the packaged `/usr/lib/...`
> systemd unit (and `/usr/local/bin` shadows `/usr/bin`), which breaks the
> daemon and CLI. On Debian and Fedora a bare `meson install` also does **not**
> create the recognition virtualenv, that is built by the `secureeye-authd`
> package at install time, so the daemon will not start. For a working system
> install, build and install your distribution's packages below.

### Debian / Ubuntu & derivatives

Expand Down Expand Up @@ -141,6 +150,50 @@ sudo dnf install ./libpam-secureeye-*.rpm ./secureeye-authd-*.rpm ./secure-eye-*
> enabled automatically. Enable it as shown in **Usage step 3b** below (the
> Debian-only `pam-auth-update` / `common-auth` steps do not apply).

### Arch Linux & derivatives

SecureEye is packaged for the AUR as `secureeye`, which builds two packages:

- `libpam-secureeye`: the C/C++ PAM module (no Python)
- `secureeye-authd`: the authentication daemon and Python recognition runtime

There is no transitional metapackage; install both. With an AUR helper:

```bash
paru -S libpam-secureeye secureeye-authd # or: yay -S ...
```

Or manually with `makepkg` (the recognition dependencies `python-mediapipe`
and `python-sounddevice` also come from the AUR and must be built first):

```bash
git clone https://aur.archlinux.org/secureeye.git
cd secureeye
makepkg -si
```

You can also build straight from a checkout of this repository:

```bash
cd secureEye/archlinux/secureEye
makepkg -si
```

Unlike the `.deb`/`.rpm` packages, the Arch build does **not** bundle a recognition virtualenv: every dependency is a
real package and the daemon runs on the system interpreter, so there is nothing to rebuild after a Python upgrade. On
`aarch64` there is no MediaPipe, so the package depends on
`python-dlib` and ships `detector_backend = dlib` in the default config.

Following Arch policy, the service is **not** started for you:

```bash
sudo systemctl enable --now secureeye-authd.service
```

> [!NOTE]
> Arch has neither `pam-auth-update` nor `authselect`, so the PAM module is
> **not** enabled automatically. Enable it as shown in **Usage step 3c** below.

---

## Usage
Expand Down Expand Up @@ -192,6 +245,19 @@ auth sufficient pam_secureEye.so

Do **not** edit `/etc/pam.d/system-auth` directly as authselect overwrites it.

**3c) Arch Linux.** There is no `pam-auth-update` and no `authselect`; edit the PAM stack yourself. For a single service
(recommended, e.g. `sudo` only), add this as the **first** `auth` line of `/etc/pam.d/sudo`:

```
auth sufficient pam_secureEye.so
```

To cover every service that includes it (login, `sudo`, display-manager greeters, `polkit`), add the same line at the
top of the `auth` section of
`/etc/pam.d/system-auth` instead. That file belongs to the `pam` package, so back it up and re-apply your change when
pacman leaves a `system-auth.pacnew`
after an upgrade.

**4. Try it.** Open a new terminal and run `sudo -i` — you should be able to
authenticate by showing your face. If face auth fails or times out, SecureEye
falls back to your password. Please check
Expand Down
54 changes: 54 additions & 0 deletions secureEye/archlinux/secureEye/.SRCINFO
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
pkgbase = secureeye
pkgdesc = Face authentication for Linux
pkgver = 0.1.3
pkgrel = 1
url = https://github.com/vhrabar/SecureEye
arch = x86_64
arch = aarch64
license = GPL-2.0-only
license = MIT
makedepends = meson>=0.64
makedepends = ninja
makedepends = pkgconf
makedepends = pam
makedepends = libevdev
makedepends = libinih
makedepends = python
makedepends = systemd
source = secureeye-0.1.3.tar.gz::https://github.com/vhrabar/SecureEye/archive/v0.1.3.tar.gz
source = secureeye-authd.install
sha256sums = c848207d44068849a13150a8b47a3d45806a9a43b9c868d1f5915ed8c2cf2d18
sha256sums = e68c31756e7196406ec24bd55cdab1af473a35c2abdd4c44a2ca9ace525beb73

pkgname = libpam-secureeye
pkgdesc = PAM module for SecureEye face authentication
depends = pam
depends = libevdev
depends = libinih
depends = gcc-libs
depends = glibc
optdepends = secureeye-authd: authentication daemon, without it the module always fails

pkgname = secureeye-authd
pkgdesc = SecureEye authentication daemon, CLI and Python runtime components
install = secureeye-authd.install
depends = python
depends = python-numpy
depends = python-opencv
depends = python-matplotlib
depends = python-cffi
depends = python-absl
depends = python-flatbuffers
depends = portaudio
depends = v4l-utils
depends = systemd
optdepends = libpam-secureeye: PAM integration, required to actually log in with SecureEye
optdepends = python-dlib: dlib recognition backend
optdepends = python-mediapipe: mediapipe recognition backend
optdepends = ffmpeg: ffmpeg camera capture backend
optdepends = python-ffmpeg-python: ffmpeg camera capture backend
optdepends = python-keyboard: hotkey rubberstamp
backup = etc/secureEye/config.ini
depends_x86_64 = python-mediapipe
depends_x86_64 = python-sounddevice
depends_aarch64 = python-dlib
112 changes: 112 additions & 0 deletions secureEye/archlinux/secureEye/PKGBUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
# Maintainer: Vedran Hrabar <vedran.hrabar@outlook.com>

pkgbase=secureeye
pkgname=('libpam-secureeye' 'secureeye-authd')
pkgver=0.1.2
pkgrel=1
pkgdesc="Face authentication for Linux"
arch=('x86_64' 'aarch64')
url="https://github.com/vhrabar/SecureEye"
license=('GPL-2.0-only' 'MIT')
makedepends=(
'meson>=0.64'
'ninja'
'pkgconf'
'pam'
'libevdev'
'libinih'
'python'
'systemd'
)
source=("$pkgbase-$pkgver.tar.gz::$url/archive/v${pkgver}.tar.gz"
'secureeye-authd.install')
sha256sums=('c848207d44068849a13150a8b47a3d45806a9a43b9c868d1f5915ed8c2cf2d18'
'e68c31756e7196406ec24bd55cdab1af473a35c2abdd4c44a2ca9ace525beb73')

_srcname="SecureEye-$pkgver"

prepare() {
cd "$_srcname"

# The deb/rpm packages run the daemon from a wheel venv built at install
sed -i 's|/usr/lib/secureeye-authd/venv/bin/python3|/usr/bin/python3|' \
secureEye/src/systemd/secureeye-authd.service.in
}

build() {
meson setup "$_srcname" build \
--wrap-mode=nodownload \
--buildtype=plain \
--prefix=/usr \
--libdir=lib \
--sysconfdir=/etc \
--localstatedir=/var \
-Dinstall_pam_config=false \
-Dconfig_dir=/etc/secureEye \
-Duser_models_dir=/etc/secureEye/models
meson compile -C build
}

package_libpam-secureeye() {
pkgdesc="PAM module for SecureEye face authentication"
depends=('pam' 'libevdev' 'libinih' 'gcc-libs' 'glibc')
optdepends=('secureeye-authd: authentication daemon, without it the module always fails')

# Install tags keep each split package to its own files.
meson install -C build --destdir "$pkgdir" --tags pam_module

install -Dm644 "$_srcname/LICENSE" "$pkgdir/usr/share/licenses/$pkgname/LICENSE"
install -Dm644 "$_srcname/licenses/MIT.txt" "$pkgdir/usr/share/licenses/$pkgname/MIT.txt"
install -Dm644 "$_srcname/NOTICE" "$pkgdir/usr/share/licenses/$pkgname/NOTICE"
}

package_secureeye-authd() {
pkgdesc="SecureEye authentication daemon, CLI and Python runtime components"
depends=(
'python'
'python-numpy'
'python-opencv'
'python-matplotlib'
'python-cffi'
'python-absl'
'python-flatbuffers'
'portaudio'
'v4l-utils'
'systemd'
)
# amd64 -> mediapipe (default backend)
# aarch64 -> dlib
depends_x86_64=('python-mediapipe' 'python-sounddevice')
depends_aarch64=('python-dlib')
optdepends=(
'libpam-secureeye: PAM integration, required to actually log in with SecureEye'
'python-dlib: dlib recognition backend'
'python-mediapipe: mediapipe recognition backend'
'ffmpeg: ffmpeg camera capture backend'
'python-ffmpeg-python: ffmpeg camera capture backend'
'python-keyboard: hotkey rubberstamp'
)
backup=('etc/secureEye/config.ini')
install=secureeye-authd.install

meson install -C build --destdir "$pkgdir" \
--tags bin,py_sources,config,systemd,bash_completion,meta,man

# user_models_dir, created by the CLI on first enrolment.
install -dm755 "$pkgdir/etc/secureEye/models"

# The systemd unit runs as the "secureeye" user; systemd's pacman hook
# creates it from this fragment.
install -Dm644 "$_srcname/secureEye/rpm/secureeye-authd.sysusers" \
"$pkgdir/usr/lib/sysusers.d/secureeye-authd.conf"

if [[ $CARCH != x86_64 ]]; then
sed -i 's/^detector_backend = mediapipe/detector_backend = dlib/' \
"$pkgdir/etc/secureEye/config.ini"
fi

install -Dm644 "$_srcname/LICENSE" "$pkgdir/usr/share/licenses/$pkgname/LICENSE"
install -Dm644 "$_srcname/licenses/MIT.txt" "$pkgdir/usr/share/licenses/$pkgname/MIT.txt"
install -Dm644 "$_srcname/NOTICE" "$pkgdir/usr/share/licenses/$pkgname/NOTICE"
install -Dm644 "$_srcname/README.md" "$pkgdir/usr/share/doc/$pkgname/README.md"
}
24 changes: 24 additions & 0 deletions secureEye/archlinux/secureEye/secureeye-authd.install
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
post_install() {
cat <<'EOF'
==> SecureEye is installed but not yet active. To finish setup:

1. Start the daemon:
systemctl enable --now secureeye-authd.service
2. Enrol a face model:
sudo secureEye add
3. Enable the PAM module (Arch has no pam-auth-update), e.g. for sudo add
this line at the top of /etc/pam.d/sudo:
auth sufficient pam_secureEye.so
Keep a root shell open while testing so a misconfiguration cannot lock
you out.

Configuration lives in /etc/secureEye/config.ini.
EOF
}

post_upgrade() {
if [ "$(vercmp "$2" 0.1.2)" -lt 0 ]; then
post_install
fi
systemctl try-restart secureeye-authd.service >/dev/null 2>&1 || true
}
28 changes: 28 additions & 0 deletions secureEye/src/auth/detector_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from __future__ import annotations

import threading
from dataclasses import dataclass
from typing import Any

Expand All @@ -10,6 +11,17 @@ class DetectorFactoryError(RuntimeError):
"""Raised when a detector backend cannot be initialized."""


# cache build detector
_detector_cache: dict[tuple, DetectorBundle] = {}
_detector_cache_lock = threading.Lock()


def _cache_key(config) -> tuple:
backend = config.get("core", "detector_backend", fallback="dlib").strip().lower()
use_cnn = config.getboolean("core", "use_cnn", fallback=False)
return (backend, use_cnn)


@dataclass
class DetectorBundle:
backend: str
Expand All @@ -23,6 +35,22 @@ def _is_missing_module(exc: ModuleNotFoundError, *candidates: str) -> bool:


def create_detector(config) -> DetectorBundle:
"""Return a detector backend for ``config``, building it once and caching it.

The first call for a given backend configuration builds the backend; every
subsequent call reuses the cached bundle.
"""
key = _cache_key(config)
with _detector_cache_lock:
cached = _detector_cache.get(key)
if cached is not None:
return cached
bundle = _build_detector(config)
_detector_cache[key] = bundle
return bundle


def _build_detector(config) -> DetectorBundle:
"""Create a detector backend from config.

Supported values for [core] detector_backend:
Expand Down
Loading
Loading