Por que um hook do OpenVPN não encontra o arquivo ao lado do script
Caminhos relativos em um hook são resolvidos contra o processo, não contra o script. Ancore-os com __file__ e depois verifique espaço de nomes e identidade antes das permissões.
Hooks que rodam sem erro e ainda assim relatam um arquivo ausente costumam olhar no lugar errado, não falhar na sua tarefa. Caminhos relativos em um script são resolvidos contra o diretório de trabalho do processo, e um serviço começa com um diretório de trabalho que você não escolheu. O arquivo ao lado do script e o arquivo que o processo abre só são a mesma coisa quando algo os ancora. Essa lacuna explica boa parte das falhas de hook que são lidas por engano como problemas de permissão.
Verifique o caminho antes das permissões, porque um erro de permissão no arquivo errado continua sendo o arquivo errado.
Um caminho relativo é resolvido contra o processo, não contra o arquivo
Quando um script chama open("policy.json"), o kernel resolve o nome contra o diretório de trabalho atual do processo. Nada da localização do próprio script participa dessa busca. OpenVPN inicia hooks a partir de um diretório que ele mesmo escolhe, e a opção cd pode alterá-lo, então um script testado à mão na própria pasta se comporta diferente quando o daemon o executa. O teste manual passa, a execução do serviço falha, e a linha de log menciona apenas o nome ausente.
O Python expõe a localização do script como __file__, e é isso que torna a ancoragem possível. Path(__file__).resolve().parent / "policy.json" constrói um caminho a partir do arquivo em vez do processo. Essa única linha remove a dependência do diretório de trabalho sem mudar nada nos privilégios.
Ancore o caminho de propósito e depois verifique o que resolve faz
resolve() segue links simbólicos, então uma instalação que liga o hook no lugar ancora no destino do link, não no diretório que contém o link. Isso costuma ser o desejado para uma implantação versionada, e é errado para um arranjo que espera dados ao lado do link simbólico. Decida o que a implantação quer dizer e escreva o código de acordo, em vez de descobrir a diferença em produção. Path(__file__).parent sem resolve() mantém o diretório próprio do link.
Se os dados pertencem à instância e não ao programa, pegue um caminho absoluto da configuração. Codificar uma localização fixa prende o hook a um único arranjo de host.
Percorra quatro fronteiras na ordem
Comece pelo diretório de trabalho: compare o WorkingDirectory, a opção cd do OpenVPN e qualquer envoltório que troque de diretório antes do exec. Depois confirme a localização resolvida, porque um arquivo de nome semelhante em outra implantação pode responder à busca e esconder o erro. Em seguida vem a visão do sistema de arquivos: um chroot ou um espaço de nomes de montagem do serviço podem simplesmente tornar ausente um caminho absoluto do host. Por último, a identidade de acesso, que é uma questão separada da visibilidade.
ProtectHome e PrivateTmp escondem localizações diferentes, e nenhum dos dois aparece numa listagem de diretório tirada do host. Um caminho que existe para root num shell pode ser invisível dentro do espaço de nomes da unidade, e é por isso que testar o mesmo comando fora do serviço prova tão pouco.
Corrija a busca sem ampliar o serviço
Ancorar o caminho resolve apenas a primeira fronteira. Não expõe um diretório que o serviço esconde nem concede a uma conta sem privilégios acesso a um arquivo só para root. Elevar script-security muda quais scripts o OpenVPN executará; não tem efeito sobre onde um nome relativo é resolvido. Mudar o diretório de trabalho de todo o serviço é mais amplo que o problema, porque qualquer outro caminho relativo de certificado, log ou auxiliar se desloca junto.
Prefira a correção estreita: um caminho determinístico no script, um valor de configuração explícito para os dados da instância e uma propriedade de arquivos que corresponda à conta com que o hook realmente roda.
Registre evidências que nomeiem a fronteira
Um relatório de falha útil contém três coisas: o caminho exato que o hook tentou, o ambiente que o produziu e qual fronteira recusou. Imprima o caminho resolvido e a exceção sem despejar o conteúdo do arquivo. Essa tríade separa uma ancoragem errada de uma montagem escondida e de uma negação de permissão, que são três correções diferentes.
A afirmação de que root pode ler o arquivo de um terminal responde a uma dessas perguntas e a nenhuma das outras. Teste, em vez disso, a partir do próprio contexto do serviço.