Compare commits

...

10 Commits

Author SHA1 Message Date
4ce9cc1fb0 feat(luks): add LUKS section
All checks were successful
Build PDF & Release / release (push) Successful in 1m41s
2026-02-14 21:57:12 +01:00
0f0ba243d5 ci: push tag
All checks were successful
Build PDF & Release / release (push) Successful in 1m37s
Co-authored-by: Gemini <gemini@google.com>
2026-02-14 02:33:16 +01:00
03031b5ca8 chores: remove unused metadata
Some checks failed
Build PDF & Release / release (push) Failing after 1m35s
2026-02-14 02:19:20 +01:00
07a101488b ci: fix path 2026-02-14 02:18:49 +01:00
69d6a42f5c chores: add version
All checks were successful
Build PDF & Release / release (push) Successful in 1m39s
2026-02-14 02:09:05 +01:00
d85f72fc92 ci: fix release version
Co-authored-by: Gemini <gemini@google.com>
2026-02-14 02:07:44 +01:00
cabb8291cb ci: remove fonts
Some checks failed
Build PDF & Release / release (push) Failing after 1m54s
2026-02-14 01:59:29 +01:00
135712e042 chores: add metadata 2026-02-14 01:58:59 +01:00
acb13112c4 ci: add config & templates
Some checks failed
Build PDF & Release / release (push) Failing after 50s
2026-02-14 01:49:18 +01:00
1ce18a4cd7 ci: add ci
Co-authored-by: Claude <claude@anthropic.com>
2026-02-14 01:48:39 +01:00
5 changed files with 494 additions and 4 deletions

85
.github/workflows/build-release.yaml vendored Normal file
View File

@@ -0,0 +1,85 @@
name: Build PDF & Release
run-name: Build PDF and Release ${{ github.ref_name }}
on:
push:
branches:
- main
paths:
- '**.md'
- '.github/workflows/**'
- 'md-pdf.ron'
jobs:
release:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
- name: Setup Rust cache
uses: swatinem/rust-cache@v2
- name: Install Typst
run: |
mkdir -p /usr/local/bin
curl -L -o typst.tar.xz https://github.com/typst/typst/releases/latest/download/typst-x86_64-unknown-linux-musl.tar.xz
tar -xJf typst.tar.xz --strip-components=1 -C /usr/local/bin/ typst-x86_64-unknown-linux-musl/typst
typst --version
- name: Install md-pdf (Rust)
run: |
cargo install md-pdf
echo "$HOME/.cargo/bin" >> $GITHUB_PATH
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: '1.23'
- name: Install svu (Go)
run: go install github.com/caarlos0/svu@latest
- name: Build PDF
run: |
md-pdf README.md
mv README.pdf PIS.pdf
# Calculate version based on commit messages (fix, feat, breaking)
- name: Calculate Next Version
id: version
run: |
NEW_TAG=$(svu next)
echo "tag=$NEW_TAG" >> $GITHUB_OUTPUT
echo "Next version : $NEW_TAG"
- name: Push Tag
run: |
git config user.name "Gitea Actions"
git config user.email "actions@gitea.local"
git tag ${{ steps.version.outputs.tag }}
git push origin ${{ steps.version.outputs.tag }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Create release and upload PDF
# Note: softprops works very well on recent Gitea
- name: Create Release
uses: softprops/action-gh-release@v1
if: ${{ steps.version.outputs.tag != '' }} # Safety check
with:
tag_name: ${{ steps.version.outputs.tag }}
name: Release ${{ steps.version.outputs.tag }}
files: PIS.pdf
draft: false
prerelease: false
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -1,3 +1,13 @@
---
title: "PIS"
subtitle: "Policy for Internal Security"
author: "Rémi Heredero "
language: "en"
tags: ["gpg", "ssh", "x509", "YubiKey", "security"]
toc: false
template: "simple"
---
# Policy for Internal Security
This repo describes my P.I.S. (**P**olicy for **I**nternal **S**ecurity).
You'll find my personal guidelines for SSH / GPG on YubiKey and how to configure and create a key / certificate.
@@ -195,11 +205,49 @@ This creates the file `id_ed25519_sk-keyring-cert.pub` that is the certificate t
---
# x509
# LUKS
It's possible to add a Yubikey as a second option to unlock a LUKS partition.
The first step is to find the encrypted partition.
```bash
lsblk
```
`nvme1n1p3` is the encrypted partition in my case.
## Enroll
Add a new way to unlock the partition with the YubiKey. This add a FIDO device, not replace the password way. You can still unlock the partition with the password if you forget the YubiKey.
This step have to be done for each Yubikey you want to use to unlock the partition.
```bash
sudo systemd-cryptenroll --fido2-device=auto /dev/nvme1n1p3
```
Actual passphrase is requested, then Yubikey Fido2 PIN, then you have to touch it 2 time to confirme presence.
## Config `/etc/crypttab`
This step have to be only once.
Backup and edit crypttab
```bash
sudo cp /etc/crypttab /etc/crypttab.bak
sudo nano /etc/crypttab
```
Add `,fido2-device=auto` (without any space) at the end of the line that describe the encrypted partition. It should look like that at the end:
```
luks-1234... UUID=1234... none discard,fido2-device=auto
```
## Re-Generate initramfs
This step have to be only once.
After enrolling the YubiKey, you need to re-generate the initramfs to be able to unlock the partition at boot time.
```bash
sudo dracut -f
```
## Master YubiKey
I create a certificate in PIV slot 9a with Yubico authenticator. This CA would be used as a Root CA for my server.
TODO fix with XCA
---

7
md-pdf.ron Normal file
View File

@@ -0,0 +1,7 @@
(
templates_dir: Some("./templates"),
default_template: Some("simple"),
default_language: Some("en"),
default_toc: Some(true),
default_author: Some("Rémi Heredero"),
)

129
templates/none.typ Normal file
View File

@@ -0,0 +1,129 @@
#import "@preview/cmarker:0.1.7"
#import "@preview/mitex:0.2.6": mitex
// Get system inputs
#let filepath = sys.inputs.at("filepath", default: "input.md")
#let language = sys.inputs.at("language", default: "en")
#let show-toc = sys.inputs.at("toc", default: "false") == "true"
// Front matter inputs
#let has-frontmatter = sys.inputs.at("has_frontmatter", default: "false") == "true"
#let fm-title = sys.inputs.at("fm_title", default: none)
#let fm-subtitle = sys.inputs.at("fm_subtitle", default: none)
#let fm-author = sys.inputs.at("fm_author", default: none)
#let fm-date = sys.inputs.at("fm_date", default: none)
#let fm-tags = sys.inputs.at("fm_tags", default: none)
#let fm_version = sys.inputs.at("fm_version", default: none)
// Parse tags from comma-separated string
#let tags-list = if fm-tags != none { fm-tags.split(",") } else { () }
// Extract filename from filepath (remove path and .md extension)
#let filename = {
let path-parts = filepath.split("/")
let file = path-parts.last()
if file.ends-with(".md") {
file.slice(0, file.len() - 3)
} else if file.ends-with(".temp.md") {
file.slice(0, file.len() - 8)
} else {
file
}
}
// Use front matter data or defaults
#let document-author = if fm-author != none { fm-author } else { default_author }
#let document-title = if fm-title != none { fm-title } else { filename }
#let document-subtitle = if fm-subtitle != none { fm-subtitle } else { filename }
// Parse date
#let document-date = if fm-date != none {
// Try to parse the date string
let date-str = fm-date
if date-str.len() == 10 and date-str.contains("-") {
// Format: YYYY-MM-DD
let parts = date-str.split("-")
if parts.len() == 3 {
datetime(year: int(parts.at(0)), month: int(parts.at(1)), day: int(parts.at(2)))
} else {
datetime.today()
}
} else {
datetime.today()
}
} else {
datetime.today()
}
// Set document properties
#set document(
author: if document-author != none { document-author } else { "" },
title: document-title,
keywords: if fm-tags != none { (if document-author != none { document-author } else { "" }, document-title, "md-pdf", ..tags-list) } else { (if document-author != none { document-author } else { "" }, document-title, "md-pdf") },
date: document-date
)
// Set document language
#set text(lang: language)
// Function to create tag labels
#let badge(content) = {
let color = rgb("888888")
let textcolor = rgb("222222")
box(
inset: (x: 3pt, y: 2pt),
radius: 4pt,
fill: color.lighten(70%),
stroke: (paint: color, thickness: 0.5pt),
)[
#text(weight: "bold", size: 6pt, fill:textcolor)[#content]
]
}
// Show basic document metadata if front matter exists
#if has-frontmatter [
#if fm-title != none [
#align(center)[
#text(size: 18pt, weight: "bold")[#fm-title]
]
#v(0.3em)
]
#if fm-subtitle != none [
#align(center)[
#text(size: 14pt, style: "italic")[#fm-subtitle]
]
#v(0.3em)
]
#let metadata = ()
#if document-author != none { metadata.push(document-author) }
#if document-date != none { metadata.push(document-date.display()) }
#if fm_version != none { metadata.push(fm_version) }
#align(center)[
#for (i, data) in metadata.enumerate() [
#data
#if i < metadata.len() - 1 [ \- ]
]
]
#if fm-tags != none and tags-list.len() > 0 [
#align(center)[
#for (i, tag) in tags-list.enumerate() [
#badge(tag.trim())
]
]
]
#line(length: 100%, stroke: 0.5pt)
]
// Show table of contents if requested
#if show-toc [
#outline()
#pagebreak()
]
#cmarker.render(
read(filepath),
scope: (image: (path, alt: none) => image(path, alt: alt)),
math: mitex
)

221
templates/simple.typ Normal file
View File

@@ -0,0 +1,221 @@
#import "@preview/cmarker:0.1.8"
#import "@preview/mitex:0.2.6": mitex
#import "@preview/hei-synd-thesis:0.2.3": *
// Get system inputs
#let filepath = sys.inputs.at("filepath", default: "input.md")
#let language = sys.inputs.at("language", default: "en")
#let show-toc = sys.inputs.at("toc", default: "false") == "true"
// Front matter inputs
#let has-frontmatter = sys.inputs.at("has_frontmatter", default: "false") == "true"
#let fm-title = sys.inputs.at("fm_title", default: none)
#let fm-subtitle = sys.inputs.at("fm_subtitle", default: none)
#let fm-author = sys.inputs.at("fm_author", default: none)
#let fm-date = sys.inputs.at("fm_date", default: none)
#let fm-tags = sys.inputs.at("fm_tags", default: none)
#let fm_version = sys.inputs.at("fm_version", default: none)
// Parse tags from comma-separated string
#let tags-list = if fm-tags != none { fm-tags.split(",") } else { () }
// Extract filename from filepath (remove path and .md extension)
#let filename = {
let path-parts = filepath.split("/")
let file = path-parts.last()
if file.ends-with(".md") {
file.slice(0, file.len() - 3)
} else if file.ends-with(".temp.md") {
file.slice(0, file.len() - 8)
} else {
file
}
}
// Use front matter data or defaults
#let document-author = if fm-author != none { fm-author } else { none }
#let document-title = if fm-title != none { fm-title } else { filename }
#let document-subtitle = if fm-subtitle != none { fm-subtitle } else { none }
// Parse date
#let document-date = if fm-date != none {
// Try to parse the date string
let date-str = fm-date
if date-str.len() == 10 and date-str.contains("-") {
// Format: YYYY-MM-DD
let parts = date-str.split("-")
if parts.len() == 3 {
datetime(year: int(parts.at(0)), month: int(parts.at(1)), day: int(parts.at(2)))
} else {
datetime.today()
}
} else {
datetime.today()
}
} else {
datetime.today()
}
// Set document properties
#set document(
author: if document-author != none { document-author } else { "" },
title: document-title,
keywords: if fm-tags != none { (if document-author != none { document-author } else { "" }, document-title, "md-pdf", ..tags-list) } else { (document-author, document-title, "md-pdf") },
date: document-date
)
// basic properties
#set page(margin: (top:3cm, bottom:3cm, left:3cm, right:2.5cm))
// header and footer
#set page(
header: context(if here().page() >=2 [
#set text(small)
#smallcaps[#document-title] #if document-subtitle != none {[\/ #document-subtitle ]}
//#line(start: (-0.5em, 0cm), length: 85%, stroke: 0.5pt)
#line(start: (-0.5em, 0cm), length: 101%, stroke: 0.5pt)
]),
footer: context( if here().page() >=2 [
#set text(small)
#line(start: (85%, 0cm), length: 15%, stroke: 0.5pt)
#document-author / #document-date.display() #h(1fr) #context counter(page).display("1 / 1", both: true)
]),
)
// font & language
#set text(
font: (
"Libertinus Serif",
"Fira Sans",
),
fallback: true,
lang: language
)
// heading
#show heading: set block(above: 1.2em, below: 1.2em)
#set heading(numbering: "1.1")
#show heading.where(level: 1): (it) => {
set text(size: larger-p )
set block(above: 1.2em, below: 1.2em)
if it.numbering != none {
let num = numbering(it.numbering, ..counter(heading).at(it.location()))
let prefix = num + h(0.5em) + text(code-border)[|] + h(0.5em)
unshift-prefix(prefix, it.body)
} else {
it
}
}
#show heading.where(level: 2): (it) => {
if it.numbering != none {
let num = numbering(it.numbering, ..counter(heading).at(it.location()))
unshift-prefix(num + h(0.8em), it.body)
}
}
// link color
#show link: it => text(fill:hei-blue, it)
// code blocks
#show raw: set text(
font: (
"Iosevka",
"Fira Code",
"JetBrains Mono",
"DejaVu Sans Mono",
),
fallback: true,)
#show raw.where(block: false): set text(weight: "semibold")
#show raw.where(block: true): set text(size: tiny)
#show raw.where(block: true): it => {
block(
fill: code-bg,
width:100%,
inset: 7pt,
radius: (left:0pt, right: 4pt),
stroke: (left: 3pt + luma(80%), rest: 0.1pt + code-border),
it,
)
}
#show: codly-init.with()
#codly(
display-icon: false,
languages: codly-languages,
zebra-fill: none,
stroke: 0.1pt + code-border,
radius: 4pt,
number-format: (number) => text(luma(210), size:7pt, [#h(1em)#number]),
inset: (left:-0.0em, rest:0.3em),
fill: code-bg,
)
// Captions
#set figure(numbering: "1", supplement: get-supplement)
#set figure.caption(separator: " - ") // With a nice separator
#set math.equation(numbering: "(1)", supplement: i18n("equation-name"))
#show: word-count
// Function to create tag labels
#let badge(content) = {
let color = rgb("888888")
let textcolor = rgb("222222")
box(
inset: (x: 3pt, y: 2pt),
radius: 4pt,
fill: color.lighten(70%),
stroke: (paint: color, thickness: 0.5pt),
)[
#text(weight: "bold", size: 8pt, fill:textcolor)[#content]
]
}
// Show basic document metadata if front matter exists
#if has-frontmatter [
#if fm-title != none [
#align(center)[
#text(size: 18pt, weight: "bold")[#fm-title]
]
#v(0.3em)
]
#if fm-subtitle != none [
#align(center)[
#text(size: 14pt, style: "italic")[#fm-subtitle]
]
#v(0.3em)
]
#let metadata = ()
#if document-author != none { metadata.push(document-author) }
#if document-date != none { metadata.push(document-date.display()) }
#if fm_version != none { metadata.push(fm_version) }
#align(center)[
#for (i, data) in metadata.enumerate() [
#data
#if i < metadata.len() - 1 [ \- ]
]
]
#if fm-tags != none and tags-list.len() > 0 [
#align(center)[
#for (i, tag) in tags-list.enumerate() [
#badge(tag.trim())
]
]
]
#line(length: 100%, stroke: 0.5pt)
]
// Show table of contents if requested
#if show-toc [
#outline()
#pagebreak()
]
#cmarker.render(
read(filepath),
scope: (image: (path, alt: none) => image(path, alt: alt)),
math: mitex
)