her.esy.fun/src/posts/project-el/index.org

171 lines
5.8 KiB
Org Mode
Raw Normal View History

2019-07-28 16:44:25 +00:00
#+TITLE: Autoload Script by project
#+SUBTITLE: fast, secure, easy autoload
#+AUTHOR: Yann Esposito
2019-07-29 22:10:54 +00:00
#+EMAIL: yann@esposito.host
2019-08-31 22:04:23 +00:00
#+DATE: [2019-08-18 Sun]
2019-07-28 16:44:25 +00:00
#+KEYWORDS: programming, blog, org-mode
#+OPTIONS: auto-id:t
#+begin_quote
/tl;dr/: A script that use projectile and GPG to securely load
an emacs lisp script when opening a new project.
Check the [[#solution]] section to get the code.
#+end_quote
* Problem
:PROPERTIES:
:CUSTOM_ID: problem
:END:
When providing a repository containing only org files of my blog.
I also wanted to provide everything necessary for users to be able to publish my
website.
Emacs, org-publish mostly assume you should put all those details in a
centralised place in your =~/.emacs.d/.init.el= file.
The main principle is quite simple.
2019-08-20 13:35:54 +00:00
1. When opening a new file in a project, check for the presence of a
=project.el= and =project.el.sig= file at the root directory of the
project.
2. Check the file was signed with by a trusted fingerprint.
3. Load the file.
Other solutions I found on the internet asked you each time you enter in a
project if you trust the file or not.
2019-07-28 16:44:25 +00:00
This was both quite annoying and insecure as it is kind of easy to type 'y'
2019-08-20 13:35:54 +00:00
instead of 'n' and to load 3rd party script.
2019-07-28 16:44:25 +00:00
2019-08-20 13:35:54 +00:00
Note that checking who signed a file with an external signature is not as
straightforward as it should be:
2019-07-28 16:44:25 +00:00
#+begin_src elisp
2019-08-20 13:35:54 +00:00
(defun auto-load-project/get-sign-key (file)
"Return the fingerprint of they key that signed FILE.
To sign a file you should used
`gpg --local-user my@email --output project.el.sig --detach-sign project.el`"
2019-07-28 16:44:25 +00:00
(string-trim-right
(shell-command-to-string
(concat
2019-08-20 13:35:54 +00:00
(format "gpg --status-fd 1 --verify %s.sig %s 2>/dev/null " file file)
"|grep VALIDSIG"
"|awk '{print $3}'"))))
2019-07-28 16:44:25 +00:00
#+end_src
- The `--status-fd` should provide more script friendly output.
GPG provide localized output by default which are therefore hard to use in
script (for grep for example).
We use =projectile= to detect the project-root and when we are in a new project.
Unfortunately the =projectile-after-switch-project-hooks= doesn't work as I
expected.
So I use the hooks =find-file-hook= and =dired-mode-hook= to try to load the
file.
In order not to load the code each time, I need to keep a local state of project
already loaded.
2019-08-20 13:35:54 +00:00
So now, each time I modify the =project.el= I sign it with the following
command line:
#+begin_src bash
gpg --local-user my@email --output project.el.sig --detach-sign project.el
#+end_src
2019-07-28 16:44:25 +00:00
* Solution
:PROPERTIES:
:CUSTOM_ID: solution
:END:
2019-08-20 13:35:54 +00:00
The project is hosted here: https://gitlab.esy.fun/yogsototh/auto-load-project-el
You can setup the emacs package in spacemacs with:
2019-07-30 09:49:03 +00:00
2019-07-28 16:44:25 +00:00
#+begin_src elisp
2019-08-20 13:35:54 +00:00
;; ...
dotspacemacs-additional-packages
'((auto-load-project :location
(recipe
:fetcher git
:url "https://gitlab.esy.fun/yogsototh/auto-load-project-el"
:files ("auto-load-project.el"))))
;; ...
(defun dotspacemacs/user-config ()
;; ...
(require 'auto-load-project)
(setq auto-load-project/trusted-gpg-key-fingerprints
'("0000000000000000000000000000000000000000" ;; figerprint of trusted key 1
"1111111111111111111111111111111111111111"
"2222222222222222222222222222222222222222"
)))
;; ...
2019-07-28 16:44:25 +00:00
#+end_src
2019-08-20 13:56:05 +00:00
The full current code should be easy to follow if you have basic notions
of eLISP:
#+begin_src elisp
(require 'projectile)
(defvar auto-load-project/trusted-gpg-key-fingerprints
'()
"The list of GPG fingerprint you trust when decrypting a gpg file.
You can retrieve the fingerprints of your own private keys
with: `gpg --list-secret-keys' (take care of removing the
spaces when copy/pasting here)")
(defun auto-load-project/get-sign-key (file)
"Return the fingerprint of they key that signed FILE.
To sign a file you should used
`gpg --local-user my@email --output project.el.sig --detach-sign project.el`"
(string-trim-right
(shell-command-to-string
(concat
(format "gpg --status-fd 1 --verify %s.sig %s 2>/dev/null " file file)
"|grep VALIDSIG"
"|awk '{print $3}'"))))
(defun auto-load-project/trusted-gpg-origin-p (file)
"Return non-nil if the FILE is encrypted with a trusted key."
(member (auto-load-project/get-sign-key file)
auto-load-project/trusted-gpg-key-fingerprints))
(defconst auto-load-project/project-file "project.el"
"Project configuration file name.")
(defvar auto-load-project/loaded-projects (list)
"Projects that have been loaded by `auto-load-project/load'.")
(defun auto-load-project/load ()
"Loads the `auto-load-project/project-file' for a project.
This is run once the project is loaded signifying project setup."
(interactive)
(when (projectile-project-p)
(lexical-let* ((current-project-root (projectile-project-root))
(project-init-file (expand-file-name auto-load-project/project-file current-project-root))
(project-sign-file (concat project-init-file ".sig")))
(when (and (not (member current-project-root auto-load-project/loaded-projects))
(file-exists-p project-init-file)
(file-exists-p project-sign-file)
(auto-load-project/trusted-gpg-origin-p project-init-file))
(message "Loading project init file for %s" (projectile-project-name))
(condition-case ex
(progn (load project-init-file)
(add-to-list 'auto-load-project/loaded-projects current-project-root)
(message "%s loaded successfully" project-init-file))
('error
(message
"There was an error loading %s: %s"
project-init-file
(error-message-string ex))))))))
(add-hook 'find-file-hook #'auto-load-project/load t)
(add-hook 'dired-mode-hook #'auto-load-project/load t)
(provide 'auto-load-project)
#+end_src