Electronic Personnel Attendance System
un sistema di rilevazione delle presenze/assenze del personale dipendente del CNR
ottobre 2021 - Workshop ICTalk@CNR 2021
Cristian Lucchesi, Maurizio Martinelli, Dario Tagliaferri <nome.cognome@iit.cnr.it>
descrivere l’architettura ed i componenti
illustrare le integrazioni con gli altri sistemi CNR
mostrare strumenti/framework software utilizzati
linguaggi e framework
strumenti di CI/CD, di logging e monitoring impiegati
spunti e riferimenti per lo sviluppo di soluzioni in-house e/o per estendere ePAS
ePAS ad oggi è utilizzato da
oltre 360 sedi CNR
~7k dipendenti CNR
tutto il personale INAF
tutto il personale LAMMA
software riutilizzabile da più sedi / istituti / enti di ricerca
configurabile per le specificità locali / istituzionali
con vincoli e controlli contrattuali stringenti
concordati e verificati dall’Ufficio del Personale
software dinamico nel tempo
no dipendenze da software commerciali
si all’utilizzo di strumenti opensource
controllo completo sviluppo e mantenimento con competenze in-house CNR
integrato con altre applicazioni CNR as much as possible
soluzione scalabile negli utenti e nel tempo
web app centralizzata per l’accesso dei dipendenti
REST server centralizzato per la ricezione delle timbrature
unico database clusterizzato
molti "ePAS client"
leggono timbrature da lettori e inviano via REST
alcuni proxy/relaying party shibboleth per gestione https e autenticazione centralizzata
unico codebase (monolite) per app principale
Play framework! Java, JPA/Hibernate, Groovy Template
~72.000 LoC Java
~27.000 LoC Groovy Template/HTML
~2600 LoC Javascript
~9.300 Statement SQL (175 evoluzioni DB)
182 tabelle SQL (metà di storico / Envers)
Kraken Listener: ricezione missioni da AMQP ed invio REST
Spring Boot, Java
Telework-stampings: gestione (REST) dati telelavoro
Micronaut Framework, Java
Perseo: scaricamento anagrafiche sedi e personale da Siper
Play framework!, Java, JPA/Hibernate, Groovy Template
CNR DCP Proxy: preleva i dati da DCP e li fornisce in REST
Python, Flask framework, Beautiful Soup
un solo monolite è difficile da mantenere
cambi una virgola in una classe nel package X e muore una oscura feature nel package Y
quasi impossibile venirne a capo anche con i test
no integrità referenziale con APP diverse
necessità di definire bene identificatori entità esterne e API con altre applicazioni
API Versioning fondamentale per mantenimento nel tempo
auth e authz sono un delirio in APP distribuite
senza un sistema di auth centralizzato (OpenID?) e authz ben definito
ruoli per sede - configurazione self service
utilizzo di JBoss Drools - Business Rules Management System (BRMS)
basato sull’algoritmo ReTe permette di processare moltissime condizioni in tempi brevissimi
integrato (AOP) in ogni chiamata di metodi pubblici
rule managePersonDayInOffice
when
$uro: UsersRolesOffices(role.name in ("personDayReader", "restClient"))
$o: Office() from $uro.office
$c: PermissionCheck(action in (
"rest.PersonDays.getDaySituation",
"rest.PersonDays.getMonthSituation",
"rest.Persons.peopleList",
"rest.v2.Certifications.getMonthSituation",
"rest.v2.Certifications.getMonthSituationByOffice",
"rest.v2.Certifications.getMonthValidationStatusByOffice",
"rest.v2.Certifications.getMonthValidationStatusByPerson",
"rest.v2.PersonDays.getDaySituationByOffice",
"rest.v2.PersonDays.getMonthSituationByOffice",
"rest.v2.PersonDays.getDaySituation",
"rest.v2.ContractWorkingTimeTypes.show",
"rest.v2.WorkingTimeTypes.show",
"rest.v2.WorkingTimeTypes.list",
"rest.v3.PersonDays.getDaySituationByOffice",
"rest.v3.PersonDays.getMonthSituationByOffice",
"rest.v3.PersonDays.getDaySituation",
"rest.v3.PersonDays.getMonthSituationByPerson",
"rest.v3.PersonDays.offSiteWorkByPersonAndMonth",
"rest.v3.PersonDays.offSiteWorkByOfficeAndMonth",
"rest.v2.Contracts.byPerson",
"rest.v2.Contracts.show",
"rest.v2.Leaves.byPersonAndYear",
"rest.v2.Leaves.byOfficeAndYear",
"rest.v2.Leaves.byOfficeAndYear.show",
"rest.v3.Vacations.byPersonAndYear",
"rest.Absences.absencesInPeriod",
"rest.Absences.attachment"
), target == $o, granted == false)
then
$c.grant();
end
circa 140 rule definite
no ePAS click day ePAS (no busta paga day come SIPER :-))
utilizzato tutti i giorni da migliaia di dipendenti
adesso ~6000 timbrature al giorno da tutta Italia
> 7 milioni di timbrature registrate
> 10 milioni di rendiconti giornalieri registrati
~500 email inviate al giorno
nessuna sessione condivisa tra istanze dei server ePAS: sessione lato client
possibilità di incrementare i server orizzontalmente
il DB può essere l’unico collo di bottiglia
non lo è per ora
varie tecniche di scalabilità applicabili
multimaster, più slave per letture, sharding, etc
i log centralizzati in un server Graylog
Graylog: opensource, dati su ElasticSearch, interfaccia WEB
sono inviati via UDP in formato GELF
log di tipo ERROR inviati ad un canale Slack
realtime troubleshooting :-)
log per richieste lente (oltre 700 ms)
metriche applicative nel formato
integrazione tramite la libreria Micrometer
esportate anche metriche di sistema (Load, CPU, Memoria, etc)
metriche prelevate in Pull da un Prometheus Server
Alert Manager configurato per inviare alert su alcune soglie (carico alto, richieste lente, server down)
alert inviati su canale Slack
docker & docker-compose per deploy
no more: it works on my machine
configurazione a partire da variabili di ambiente docker
version: '3.1'
############################################################################
# ePAS Server #
# Configurazione per l'avvio di ePAS e di un'eventuale postgres locale #
############################################################################
services:
### Le variabili volumes sono mappate come - host_folder:container_folder
### Correggere all'occorrenza i path per le cartelle sull'host ma lasciare
### inalterati i path sui container docker
epas:
image: consiglionazionalericerche/epas
container_name: epas
volumes:
- ./epas/logs:/home/epas/epas/logs
- ./epas/attachments:/home/epas/epas/data/attachments
- /etc/localtime:/etc/localtime:ro
- /etc/timezone:/etc/timezone:ro
#Non è necessario esporre la porta 9000 se si utilizza un proxy per
#servire le richieste http/https. L'utilizzo del proxy https è fortemente
#consigliato in produzione, utilizzare per esempio traefik.
ports:
- 9000:9000
#labels:
# - traefik.enable=true
# - traefik.backend=epas
# - traefik.frontend.rule=Host:${HOST_NAME}
# - traefik.port=9000
environment:
- VIRTUAL_HOST=${HOST_NAME}
#- PROTOCOL=https # default: http -- (http,https)
##- EPAS_SHIB_LOGIN= # default: false -- (true,false)
- JOBS_ACTIVE=true # default: false -- (true,false) -- Se forzato a true abilita l'esecuzione di tutti i job
#- SKIP_IP_CHECK= # default: false -- (true,false) -- Disabilita il controllo sugli indirizzi ip delle richieste
- FLOWS_ACTIVE=true
#- URL_ATTESTATI= # default: https://attestativ2.rm.cnr.it
#- URL_USER=
#- URL_PASS=
######## LOGS ###########
#- LOG_LEVEL= # Opzionale. default: INFO -- (OFF,FATAL,ERROR,WARN,INFO,DEBUG,TRACE,ALL)
- APPENDERS=file # Opzionale. default: stdout, stderr -- (stdout, stderr, file, graylog2). Abilita i log sulla console, file e server graylog
#- GRAYLOG_HOST= # Obbligatorio se attivato log sull'appender graylog2. default: null
#- GRAYLOG_PORT= # Opzionale. default: 3514
#- GRAYLOG_ORIGIN_HOST= # Opzionale. default: valore in VIRTUAL_HOST
###### Container ########
#- BACKUP_CRON= # default: disattivato. (utilizzare il format del crontab. Es. 0 0 * * *)
#- CERT_NAME= # default: valore specificato in VIRTUAL_HOST -- Specifica un nome diverso per i file del certificato SSL
- TZ=Europe/Rome
#### Connessione DB ####
- DB_HOST=${DB_HOST} # default: indirizzo assegnato al container postgres linkato
- DB_NAME=${DB_NAME} # default: epas
- DB_PASS=${DB_PASS} # Obbligatoria. Password per accedere al database server
#- DB_PORT=5432 # default: 5432
#- DB_USER=${DB_USER} # default: postgres
#### server SMTP ####
- SMTP_HOST=${SMTP_HOST} # default: smtp.cnr.it
#- SMTP_PORT=${SMTP_PORT} # default: 25 se SMTP_CHANNEL è impostato clear o starttls; 465 se impostato su ssl
##- SMTP_CHANNEL=${SMTP_CHANNEL} # default: clear -- (clear, ssl ,starttls)
#- SMTP_FROM=${SMTP_FROM} # default: epas@cnr.it -- Indirizzo utilizzato per il campo mittente delle mail inviate dal sistema
#- SMTP_PROTOCOL=${SMTP_PROTOCOL} # default: smtp -- (smtp, smtps)
#- SMTP_USER=${SMTP_USER} # user utilizzato per l'autenticazione sul server smtp (se necessario)
#- SMTP_PASS=${SMTP_PASS} # password utilizzato per l'autenticazione sul server smtp (se necessaria)
#### Autenticazione LDAP ####
#- LDAP_LOGIN=true # default: false. Impostare a true per attivare l'autenticazione tramite LDAP
#- LDAP_URL=${LDAP_URL} # url del server LDAP, per esempio ldap://ldap.cnr.it:389
#- LDAP_STARTTLS=${LDAP_STARTTLS} # default: false. Se impostato a true attiva la connessione cifrata tramite il protocollo starttls
##- LDAP_TIMEOUT=${LDAP_TIMEOUT} # default: 1000. Time in millisecondi della connessione LDAP.
#- LDAP_DN_BASE=${LDAP_DN_BASE} # DN per la ricerca degli utenti su LDAP, per esempio ou=People,dc=iit,dc=cnr,dc=it
##- LDAP_LOGIN_RETURN=${LDAP_LOGIN_RETURN} # default: /. Indirizzo relativo di reindirizzamento dopo il login LDAP.
##- LDAP_EPPN_ATTRIBUTE_NAME=${LDAP_EPPN_ATTRIBUTE_NAME} # default: eduPersonPrincipalName. Campo LDAP utilizzato per il mapping con il campo eppn presente in ePAS.
### I due parametri successivi sono da impostare solo nel caso sia necessario effettuare la prima connessione ad LDAP con un utente privilegiato.
##- LDAP_BIND_DN=${LDAP_BIND_DN} # Eventuale DN dell'utente LDAP privilegato
##- LDAP_BIND_CREDENTIALS=${LDAP_BIND_CREDENTIALS} # Eventuale password dell'utente LDAP privilegato
##- LDAP_AUTHENTICATE_USER_SEARCH_DN=${LDAP_AUTHENTICATE_USER_SEARCH_DN} # Da utilizzare solo è presente un LDAP_BIND_DN. L'utente viene cercato su LDAP con
# l'utente amministratore e poi verificata l'autenticazione facendo una search LDAP
# con le credenziali utente con contesto uguale a questo parametro. Es. o=cnr,c=it
#### Invio Segnalazioni via email
#- REPORT_TO=${REPORT_TO} # default: epas@iit.cnr.it
#- REPORT_FROM=${REPORT_FROM} # default: segnalazioni@epas.tools.iit.cnr.it
#- REPORT_SUBJECT=${REPORT_SUBJECT} # default: Segnalazione ePAS
#- TELEWORK_ACTIVE=${TELEWORK_ACTIVE} # default: false
#- TELEWORK_BASEURL=${TELEWORK_BASEURL} # default: http://telework-stampings:8080
#- TELEWORK_USER=${TELEWORK_USER} # default: app.epas
#- TELEWORK_PASS=${TELEWORK_PASS} # default: chiedere al team di sviluppo
restart: unless-stopped
postgres:
image: postgres:12
environment:
- POSTGRES_PASSWORD=${DB_PASS}
volumes:
- ./postgres/data:/var/lib/postgresql/data
- /etc/localtime:/etc/localtime:ro
- /etc/timezone:/etc/timezone:ro
restart: unless-stopped
ePAS rilasciato con licenza Opensource
pubblicato su GitHub
prima release pubblica la version 2.0.0 del'8 marzo 2021
GNU Affero General Public License v3.0
realizzata nuova documentazione di ePAS
utilizzato formato Agid tramite template Sphinx (thanks to @mspasiano)
scritta in reStructuredText
non può contenere tutte le informazioni del personale
non è un sistema di rendicontazione dei progetti
non è un sistema di rilevamento delle persone presenti in sede
deve assicurare l’interoperabilità e la cooperazione applicativa con altri sistemi
servizi REST sono la modalità per interfacciarsi con ePAS
rilasciati su Github con licenza AGPL v3 anche i client:
questa presentazione: https://epas.projects.iit.cnr.it/presentazioni/epas-workshop-ictalk@cnr-2021.html