Executar unha acción posterior á conexión VPN como o usuario, non como o daemon
Unha variable de contorno que di USER=alice non cambia con que conta se executa un hook. Separa o evento, o disparador e a acción, e despois demostra o UID.
Os hooks de conexión dun daemon VPN execútanse como o daemon, e normalmente iso significa que se executan como root. No momento en que un deses hooks escribe no directorio persoal de alguén ou reinicia un asistente de escritorio, dáslle a un evento de rede a capacidade de actuar como un usuario local arbitrario. O atallo habitual, ler un nome de usuario dunha variable de contorno e pasalo, non arranxa iso. Só reetiqueta a acción sen cambiar que conta a executa. O que funciona é unha entrega en tres partes onde o paso privilexiado di que ocorreu e unha unidade separada fai a acción coa identidade correcta.
Un nome de usuario no contorno non é un conmutador de identidade
Unix decide a que pode tocar un proceso polo seu ID de usuario efectivo, non polas cadeas do seu contorno. Establecer USER=alice ou exportar LOGNAME cambia o que imprime un software descoidado na súa saída; non cambia nada nas comprobacións de propiedade de ficheiros. Un hook que se executa como root con esa variable establecida segue abrindo os ficheiros de Alice como root.
Os nomes tampouco son o mesmo nome. O inicio de sesión de acceso remoto, a conta Unix local e o nome común do certificado son tres identificadores que a miúdo coinciden e ás veces non. Un usuario que se autentica como a.smith pode non ter ningunha conta local, ou pode mapearse a asmith mediante un servizo de directorio. Calquera deseño que asuma que unha cadea serve para os tres papeis romperá co primeiro contratista.
Así que a primeira pregunta non é «que usuario está conectado» senón «que conta local ten permiso para realizar esta acción». Responde iso desde a túa propia base de datos de contas, non desde o que enviase o cliente:
id -u alice
getent passwd alice | cut -d: -f1,3,6
stat -c '%U:%G %a %n' /home/alice/.local/state/vpn-status.json
O terceiro comando é o que a xente salta. Se o ficheiro obxectivo é propiedade de root porque o hook anterior o creou, unha acción de usuario ben configurada fallará cun erro de permisos que parece un problema de systemd e non o é.
O daemon queda onde está
Cambiar o User= propio do servizo VPN a Alice é a panca equivocada. Altera como o daemon liga interfaces, le claves, escribe o seu ficheiro de estado e xestiona rutas, e traslada todo ese risco a unha unidade que funcionaba. O daemon necesita os seus privilexios; a acción posterior non.
Mira que fai realmente a unidade antes de tocarla. Dous comandos dinche a identidade de execución e as liñas de comando exactas en vigor, incluído todo o que engadise unha anulación:
systemctl show -p User -p Group -p ExecStart openvpn-server@server
systemctl cat openvpn-server@server
Nunha instalación de paquete predeterminada o daemon é root e o script --up herda iso. Confírmalo en lugar de asumilo, porque unha distribución endurecida pode terse movido a unha conta dedicada, e un drop-in escrito a man pode executar o hook a través dun envoltorio con regras distintas.
Tres partes con nome, un disparador estreito
O arranxo que sobrevive ao mantemento ten un evento, un disparador e unha acción. O daemon informa de que subiu unha sesión con nome. Un disparador acepta ese pequeno feito e pide unha acción fixa. A acción execútase baixo a conta local que posúe os ficheiros que toca. Nada na cadea acepta unha ruta de comando arbitraria.
A acción en si é unha unidade de servizo ordinaria, e a identidade é unha soa directiva dentro dela:
[Service]
Type=oneshot
User=alice
Group=alice
ExecStart=/usr/local/bin/refresh-vpn-status --slot %i
Fíxate en que User=alice se aplica a toda a unidade, e systemd documenta unha excepción que paga a pena coñecer: un comando co prefixo + execútase con todos os privilexios independentemente diso. Ese prefixo é unha ferramenta lexítima para un único paso privilexiado e unha forma cómoda de desfacer a separación que acabas de construír, así que trata calquera + nesa unidade como algo que hai que xustificar na revisión.
Mantén a entrada do disparador mínima e non secreta. O identificador de sesión, a conta mapeada e unha marca de tempo adoitan ser todo o necesario. Nunca pases un fragmento de shell, unha ruta de executábel ou un bloque de contorno proporcionado polo cliente a través da fronteira, porque o lado receptor non ten forma de distinguir un valor lexítimo dun inxectado.
Verifica a identidade que realmente se executou
A revisión da configuración non é evidencia. Tras unha conexión de proba aprobada, confirma o UID que tocou o ficheiro e o UID que o daemon segue usando:
stat -c '%U %G %y %n' /home/alice/.local/state/vpn-status.json
ps -o user=,pid=,cmd= -p "$(pgrep -o -f 'openvpn --daemon')"
systemd-run --uid=alice --pipe /usr/local/bin/refresh-vpn-status --dry-run
A primeira liña debería mostrar a Alice e unha marca de tempo dentro da túa xanela de proba. A segunda debería seguir mostrando root ou a conta de servizo do paquete, o que demostra que o daemon non se moveu. A terceira é a forma máis barata de probar os permisos da acción sen esperar unha reconexión real.
Se o propietario do ficheiro é Alice pero o seu grupo ou modo cambiou entre execucións, busca un UMask= ou unha diferenza de permisos do directorio en lugar dun erro de lóxica. Se a marca de tempo non se moveu en absoluto, o disparador nunca se disparou, e o seguinte paso útil é journalctl -u para a unidade de acción en lugar doutra reconexión do cliente.
Que segue sen demostrar un evento de conexión
Un túnel establecido é un só feito. Non significa que a sesión de escritorio estea desbloqueada, que o bus de sesión sexa accesíbel ou que o recurso privado que quere a acción xa estea dispoñible. Unha unidade oneshot baixo o UID de Alice ten a súa identidade e nada da súa sesión, así que calquera cousa que requira un bus gráfico ten que pasar polo mecanismo de sesión de usuario soportado e definir que pasa cando ela pecha a sesión.
As reconexións tamén se repiten. Un portátil que dorme nun tren pode producir unha ducia de eventos de conexión nunha hora, e un hook que engade unha liña cada vez converte un ficheiro de estado nun rexistro que ninguén pode ler. Fai a acción idempotente, ou deixa que substitúa un único rexistro con chave por sesión.
Por último, limita o radio de dano na propia unidade: unha vista do sistema de ficheiros de só lectura fóra do directorio de estado, sen privilexios novos e cun tempo de espera explícito. A separación entre daemon e acción de usuario vale a pena por si soa, pero vale moito máis cando a acción non pode facer nada excepto o único que aprobaches.