Compare commits
8
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9ffc6a14d2
|
||
|
|
19f6007113
|
||
|
|
6472890fa2
|
||
|
|
fc1b45058c
|
||
|
|
3601def1f6
|
||
|
|
1292597e09
|
||
|
|
d607e5df4d | ||
|
|
4199eb0e42 |
No files matched your search
@@ -0,0 +1,6 @@
|
||||
.git
|
||||
.delta
|
||||
.idea
|
||||
.vscode
|
||||
docs
|
||||
legacy_docs
|
||||
@@ -0,0 +1,17 @@
|
||||
# Server configuration. Copy to .env (gitignored), export or source it.
|
||||
|
||||
# Address the server listens on.
|
||||
LISTEN_ADDR=:8080
|
||||
# Session lifetime.
|
||||
SESSION_TTL=12h
|
||||
# Set to false only when testing locally over plain http.
|
||||
COOKIE_SECURE=false
|
||||
|
||||
# OIDC (Authentik). Unset: public pages still work, protected pages answer 503.
|
||||
OIDC_ISSUER_URL=https://auth.example.com/application/o/yggdrasil/
|
||||
OIDC_CLIENT_ID=yggdrasil
|
||||
OIDC_CLIENT_SECRET=change-me
|
||||
OIDC_REDIRECT_URL=http://localhost:8080/callback
|
||||
# Comma/space separated scopes. The groups claim must be covered by a scope
|
||||
# mapping on the provider for group checks to work.
|
||||
# OIDC_SCOPES=openid profile email groups
|
||||
@@ -0,0 +1,48 @@
|
||||
name: build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
|
||||
# shiroa bundles its own Typst compiler, so no separate typst install.
|
||||
- name: Install shiroa
|
||||
run: |
|
||||
curl -sSL -o /tmp/shiroa.tar.gz \
|
||||
https://github.com/Myriad-Dreamin/shiroa/releases/download/v0.4.0-rc2/shiroa-x86_64-unknown-linux-musl.tar.gz
|
||||
tar -xzf /tmp/shiroa.tar.gz -C /tmp
|
||||
install -m 755 /tmp/shiroa-x86_64-unknown-linux-musl/shiroa /usr/local/bin/shiroa
|
||||
shiroa --version
|
||||
|
||||
- name: Generate acl.json
|
||||
run: go run ./cmd/parser -in docs -out acl.json
|
||||
|
||||
- name: Build the book
|
||||
run: shiroa build docs --dest-dir ../dist
|
||||
|
||||
- name: Test and build the server
|
||||
run: |
|
||||
go vet ./...
|
||||
go test ./...
|
||||
test -z "$(gofmt -l cmd internal embed.go)" || { echo 'gofmt needed:'; gofmt -l cmd internal embed.go; exit 1; }
|
||||
CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" -o yggdrasil-server ./cmd/server
|
||||
|
||||
# upload-artifact v4 needs a reasonably current Gitea/act_runner;
|
||||
# fall back to v3 if your runner is too old to serve v4 artifacts.
|
||||
- name: Upload server artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: yggdrasil-server-linux-amd64
|
||||
path: yggdrasil-server
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
# build outputs (dist is regenerated by shiroa; .gitkeep keeps go:embed happy)
|
||||
/dist/*
|
||||
!/dist/.gitkeep
|
||||
/server
|
||||
yggdrasil-server
|
||||
|
||||
# secrets
|
||||
.env
|
||||
|
||||
# editors
|
||||
.idea/
|
||||
.vscode/
|
||||
|
||||
# delta workspace
|
||||
/.delta/
|
||||
|
||||
# pinned toolchain downloads (shiroa), managed by the Justfile
|
||||
tools/
|
||||
|
||||
# shiroa serve default output
|
||||
docs/dist/
|
||||
Generated
-17
@@ -1,17 +0,0 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="Docsify" type="ShConfigurationType">
|
||||
<option name="SCRIPT_TEXT" value="start http://localhost:3000 ; docsify serve " />
|
||||
<option name="INDEPENDENT_SCRIPT_PATH" value="false" />
|
||||
<option name="SCRIPT_PATH" value="$PROJECT_DIR$/.idea/runConfigurations/run.sh" />
|
||||
<option name="SCRIPT_OPTIONS" value="" />
|
||||
<option name="INDEPENDENT_SCRIPT_WORKING_DIRECTORY" value="true" />
|
||||
<option name="SCRIPT_WORKING_DIRECTORY" value="$PROJECT_DIR$" />
|
||||
<option name="INDEPENDENT_INTERPRETER_PATH" value="true" />
|
||||
<option name="INTERPRETER_PATH" value="powershell.exe" />
|
||||
<option name="INTERPRETER_OPTIONS" value="" />
|
||||
<option name="EXECUTE_IN_TERMINAL" value="true" />
|
||||
<option name="EXECUTE_SCRIPT_FILE" value="false" />
|
||||
<envs />
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
</component>
|
||||
@@ -0,0 +1,80 @@
|
||||
# AGENT.md — operating rules for agents working on Yggdrasil
|
||||
|
||||
Personal knowledge base: Typst sources (`docs/`, a shiroa book) compiled to
|
||||
a static site, embedded into a Go binary that enforces per-page OIDC group
|
||||
permissions (Authentik). README.md is the human-facing documentation; this
|
||||
file is the working rulebook. Follow it; when it conflicts with the user,
|
||||
the user wins.
|
||||
|
||||
## Commands
|
||||
|
||||
- `just` — shows the command overview (bare `just` never builds anything).
|
||||
- `just build` — parse ACL, compile the book, build `./server`.
|
||||
- `just run` — build everything, then serve on :8080.
|
||||
- `just check` — go vet, go test, gofmt. Run it before every commit.
|
||||
- `just clean` — removes `dist/` and `server` (keeps `tools/` and
|
||||
restores the committed `dist/.gitkeep`).
|
||||
- The shiroa toolchain is pinned and self-bootstrapped into `tools/` by the
|
||||
`tools` recipe. Never assume a global shiroa/typst install exists; do not
|
||||
install shiroa globally; do not bump the pin casually (see Traps).
|
||||
|
||||
## Layout
|
||||
|
||||
- `docs/` — shiroa book. `book.typ` lists every built page; files not listed
|
||||
there are not part of the site. Categories are `=` part headers in the
|
||||
summary, pages are `# chapter("path.typ")[Title]` entries. Final URL =
|
||||
source path with `.typ` replaced by `.html`.
|
||||
- `cmd/parser` — build step: scans `docs/` for `#let access-groups = (..)`
|
||||
tags and emits `acl.json`. Untagged pages are public.
|
||||
- `cmd/server` + `embed.go` (repo root) — `//go:embed` of `dist/` and
|
||||
`acl.json` into a single binary.
|
||||
- `internal/acl` — ACL loading and page-key normalization (security critical).
|
||||
- `internal/oidc` — OIDC authorization-code client.
|
||||
- `internal/middleware` — sessions, login/callback/logout, access gate.
|
||||
- `legacy_docs/` — frozen previous site. Never modify or migrate it unless
|
||||
the user asks explicitly.
|
||||
|
||||
## Security invariants — do not break
|
||||
|
||||
- Fail closed: a malformed `acl.json` aborts startup; protected pages with
|
||||
no configured IdP answer 503, never the content.
|
||||
- Page keys: shiroa pages keep their rendered content in sibling
|
||||
`<page>.<theme>.multi.sir.in` files fetched by client-side JS.
|
||||
`acl.PageKey` reduces any URL to a page key so those files inherit the
|
||||
page's protection. If the shiroa output format changes, re-verify this
|
||||
mapping: content in any new sidecar file must be covered or blocked.
|
||||
- `/searchindex.json` contains the text of protected pages: blocked at the
|
||||
router, always.
|
||||
- Session cookies: HttpOnly, SameSite=Lax, Secure unless COOKIE_SECURE=false.
|
||||
OAuth2 states are random and single use; post-login redirect targets are
|
||||
sanitized (open-redirect guard).
|
||||
|
||||
## Traps
|
||||
|
||||
- Shiroa version pairing: the CLI and the `@preview/shiroa:*` typst package
|
||||
must match (CLI v0.4.0-rc2 pairs with package 0.4.0; the v0.3.0 release
|
||||
CLI rejects 0.4.0 books). The pin lives in two places: the Justfile
|
||||
`shiroa-version` and `.gitea/workflows/build.yml`. Bump all of them
|
||||
together with the package import and rebuild.
|
||||
- Typst call syntax: no space between `)` and the trailing content block —
|
||||
`#chapter("x.typ")[Title]`, not `#chapter("x.typ") [Title]`.
|
||||
- `dist/` and `acl.json` are embedded at Go build time; there is no runtime
|
||||
reload. After changing docs or ACL, rebuild the binary.
|
||||
- `go build` needs `dist/` and `acl.json` to exist: fresh clones carry
|
||||
`dist/.gitkeep` and a committed `acl.json` for exactly this reason.
|
||||
Never gitignore those two.
|
||||
|
||||
## Testing expectations
|
||||
|
||||
- Test what the user runs, not a proxy for it: from a clean state
|
||||
(`rm -rf tools && just clean`), `just build` and `just run` must succeed
|
||||
with no global shiroa on PATH. Test the bare `just` output too. After
|
||||
any clean/test cycle, `git status` must be clean: `dist/.gitkeep` is
|
||||
tracked, and committing a worktree where it is missing breaks fresh
|
||||
clones (`go:embed` needs `dist/`).
|
||||
- Server behavior: curl a public page (200), a protected page (503 without
|
||||
OIDC env; it must never serve protected content), and `/searchindex.json`
|
||||
(404). Extend the unit tests in `internal/acl` and `internal/middleware`
|
||||
whenever touching those paths.
|
||||
- A live OIDC round trip needs a real IdP and is not testable here. Say
|
||||
that explicitly when reporting what was verified.
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
# Rootless image of the Yggdrasil server.
|
||||
# Build after `just parse build-docs` so that dist/ and acl.json are current:
|
||||
# just parse build-docs && docker build -t yggdrasil .
|
||||
|
||||
FROM golang:1.27 AS build
|
||||
WORKDIR /src
|
||||
COPY go.mod go.sum ./
|
||||
RUN go mod download
|
||||
COPY . .
|
||||
RUN CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" -o /out/server ./cmd/server
|
||||
|
||||
FROM gcr.io/distroless/static-debian12:nonroot
|
||||
COPY --from=build /out/server /server
|
||||
EXPOSE 8080
|
||||
USER nonroot:nonroot
|
||||
ENTRYPOINT ["/server"]
|
||||
@@ -0,0 +1,52 @@
|
||||
# Yggdrasil: Typst documentation behind an authenticated Go server.
|
||||
|
||||
# Pinned shiroa toolchain, self-managed under tools/ (no global install).
|
||||
# The CLI and the @preview/shiroa package version must match (see AGENT.md).
|
||||
shiroa-version := "v0.4.0-rc2"
|
||||
shiroa-target := if arch() == "x86_64" { "x86_64-unknown-linux-musl" } else if arch() == "aarch64" { "aarch64-unknown-linux-musl" } else { error("unsupported architecture " + arch()) }
|
||||
shiroa-dir := "tools/shiroa"
|
||||
shiroa-bin := shiroa-dir + "/shiroa-" + shiroa-target + "/shiroa"
|
||||
shiroa-url := "https://github.com/Myriad-Dreamin/shiroa/releases/download/" + shiroa-version + "/shiroa-" + shiroa-target + ".tar.gz"
|
||||
|
||||
# Show the available commands.
|
||||
default:
|
||||
@just --list
|
||||
|
||||
# Build everything: acl.json, the static site, and the server binary.
|
||||
build: parse build-docs build-server
|
||||
|
||||
# Scan docs/ for access-groups tags and regenerate acl.json.
|
||||
parse:
|
||||
go run ./cmd/parser -in docs -out acl.json
|
||||
|
||||
# Download the pinned shiroa into tools/ if missing.
|
||||
tools:
|
||||
@mkdir -p {{shiroa-dir}}
|
||||
@test -x {{shiroa-bin}} || { echo "Downloading shiroa {{shiroa-version}} ({{shiroa-target}})..."; curl -fsSL -o {{shiroa-dir}}/shiroa.tar.gz {{shiroa-url}}; tar -xzf {{shiroa-dir}}/shiroa.tar.gz -C {{shiroa-dir}}; rm {{shiroa-dir}}/shiroa.tar.gz; {{shiroa-bin}} --version; }
|
||||
|
||||
# Compile the Typst book into dist/.
|
||||
# (--dest-dir is resolved relative to the book directory, docs/, hence ../dist)
|
||||
build-docs: tools
|
||||
{{shiroa-bin}} build docs --dest-dir ../dist
|
||||
|
||||
# Build the server binary (embeds dist/ and acl.json).
|
||||
build-server:
|
||||
CGO_ENABLED=0 go build -trimpath -o server ./cmd/server
|
||||
|
||||
# Rebuild everything and run the server locally.
|
||||
run: build
|
||||
./server
|
||||
|
||||
# Live-preview the book without authentication (shiroa dev server).
|
||||
preview-docs: tools
|
||||
{{shiroa-bin}} serve docs
|
||||
|
||||
# Run static checks and tests.
|
||||
check:
|
||||
go vet ./...
|
||||
go test ./...
|
||||
@test -z "$(gofmt -l cmd internal embed.go)" || { echo 'gofmt needed:'; gofmt -l cmd internal embed.go; exit 1; }
|
||||
|
||||
# Remove build artifacts (keeps tools/ and the committed dist/.gitkeep).
|
||||
clean:
|
||||
rm -rf dist server && mkdir -p dist && touch dist/.gitkeep
|
||||
@@ -1,27 +1,200 @@
|
||||
<header>
|
||||
<h1>Yggdrasil</h1>
|
||||
</header>
|
||||
Yggdrasil is a collections of notes around IT, OT and other geeks stuff
|
||||
<br>
|
||||
<br>
|
||||
<br>
|
||||
# Yggdrasil
|
||||
|
||||
# [Bachelor in embedded system](/infotro/)
|
||||
Here notes I take during my bachelor in embedded systems
|
||||
A personal knowledge base written in [Typst](https://typst.app), compiled to
|
||||
a static site by [shiroa](https://github.com/Myriad-Dreamin/shiroa), and
|
||||
served by a small Go binary that embeds the whole site and gates protected
|
||||
pages behind OIDC login (Authentik) with per-page group permissions.
|
||||
|
||||
Mainly some topics:
|
||||
- [PTR - Programmation Temps Réel](/infotro/PTR/)
|
||||
- [SDi - Système Distribué](/infotro/SDi/)
|
||||
- [Eln - Électronique](/infotro/Eln/)
|
||||
- [SEm - Systèmes Embarqué](/infotro/SEm/)
|
||||
No search, no database, no Node.js: one binary, one configuration.
|
||||
|
||||
# [Embedded Systems](/Embed/)
|
||||
Here some note specific about some Embedded System
|
||||
- [STM32](/Embed/STM32/)
|
||||
- [nRF](/Embed/nRF/)
|
||||
## How it works
|
||||
|
||||
# [Working environment at HES-SO](/HES-SO/)
|
||||
Here some note about the working environment at HES-SO
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A["docs/*.typ (Typst sources)"] -->|"just build-docs (shiroa)"| B["dist/ (static site)"]
|
||||
A -->|"just parse (cmd/parser, access-groups tags)"| C["acl.json"]
|
||||
B -->|"go build (//go:embed)"| E["server binary"]
|
||||
C --> E
|
||||
E --> F{"request /page.html"}
|
||||
F -->|"public page"| G["serve page"]
|
||||
F -->|"protected page"| H{"session + groups ok?"}
|
||||
H -->|"no session"| I["redirect to /login, then Authentik"]
|
||||
H -->|"not authorized"| J["403"]
|
||||
H -->|"authorized"| G
|
||||
```
|
||||
|
||||
- [OneDrive - Linux](/HES-SO/OneDrive.md)
|
||||
- [Teams - Linux](/HES-SO/Teams.md)
|
||||
- Pages are written in Typst under `docs/` and listed in `docs/book.typ`.
|
||||
- A page can declare who may read it with a tag:
|
||||
`#let access-groups = ("family", "admin")`. Pages without a tag are public.
|
||||
- At build time, `cmd/parser` scans `docs/`, extracts the tags and generates
|
||||
`acl.json` mapping final page URLs to allowed groups.
|
||||
- The Go server embeds both `dist/` and `acl.json`. For every request it
|
||||
reduces the URL to a *page key* and looks it up in the ACL: public pages
|
||||
are served directly; protected pages require a session whose groups include
|
||||
at least one of the allowed groups.
|
||||
- Users authenticate with OIDC. The server reads the `groups` claim of the
|
||||
verified ID token to build the session.
|
||||
|
||||
## Repository layout
|
||||
|
||||
```
|
||||
docs/ Typst sources (shiroa book)
|
||||
book.typ book metadata + chapter list (the sidebar)
|
||||
template.typ page layout (from the shiroa minimal template)
|
||||
index.typ home page (public)
|
||||
thermo/, security/, homelab/ example categories
|
||||
cmd/parser/ generates acl.json from access-groups tags
|
||||
cmd/server/ the web server
|
||||
internal/acl/ ACL loading, page-key normalization, group checks
|
||||
internal/oidc/ OIDC client (authorization code flow)
|
||||
internal/middleware/ sessions, login/logout/callback, access gate
|
||||
dist/ compiled site (generated, embedded at build time)
|
||||
tools/ pinned shiroa binary (downloaded by `just tools`)
|
||||
acl.json generated ACL (embedded at build time)
|
||||
legacy_docs/ the old documentation base (kept for manual migration)
|
||||
.gitea/workflows/ Gitea Actions CI
|
||||
```
|
||||
|
||||
## Quick start
|
||||
|
||||
Prerequisites: Go 1.27.1+ (the version required by `go.mod`) and
|
||||
[just](https://github.com/casey/just). The
|
||||
pinned shiroa toolchain is downloaded automatically into `tools/` on the
|
||||
first build (needs `curl` + `tar` and network access); nothing is
|
||||
installed globally.
|
||||
|
||||
```sh
|
||||
just # show the available commands
|
||||
just run # parse ACL, compile the book, build and start the server
|
||||
```
|
||||
|
||||
Then open http://localhost:8080. The home page and public pages render;
|
||||
protected pages (`/thermo/fluids.html`, `/security/ssh.html`,
|
||||
`/homelab/backup.html` in the example book) return **503** until OIDC is
|
||||
configured.
|
||||
|
||||
For pure writing previews without authentication, use `just preview-docs`.
|
||||
|
||||
## Writing documentation
|
||||
|
||||
The book structure mirrors the URL structure: `docs/<path>.typ` is compiled
|
||||
to `/<path>.html`. To add a page:
|
||||
|
||||
1. Create the file, e.g. `docs/security/wireguard.typ`, starting with:
|
||||
|
||||
```typst
|
||||
#import "../template.typ": project
|
||||
#show: project
|
||||
|
||||
= WireGuard
|
||||
|
||||
Your content...
|
||||
```
|
||||
|
||||
2. Reference it in `docs/book.typ`. A `=` heading creates a category
|
||||
(part) in the sidebar; each `- #chapter(...)` is a page:
|
||||
|
||||
```typst
|
||||
= Security
|
||||
- #chapter("security/ssh.typ", section: "4")[SSH hardening]
|
||||
- #chapter("security/wireguard.typ", section: "5")[WireGuard]
|
||||
```
|
||||
|
||||
3. Only pages listed in `book.typ` are built. Keep `section:` numbers
|
||||
consistent (sub-chapters like `"4.1"` nest under `"4"`).
|
||||
|
||||
To restrict a page, add the tag on the first line of its file:
|
||||
|
||||
```typst
|
||||
// Only readable by members of these groups.
|
||||
#let access-groups = ("family", "admin")
|
||||
```
|
||||
|
||||
- The groups are compared against the `groups` claim of the OIDC ID token
|
||||
(in Authentik: your user's group memberships).
|
||||
- A page without the tag is public.
|
||||
- Everything served *for* a protected page is protected too: the shiroa
|
||||
renderer fetches page content from `*.multi.sir.in` files, and those
|
||||
reduce to the same ACL entry as the page itself.
|
||||
- The search index (`searchindex.json`) would also contain protected text,
|
||||
so the server never serves it.
|
||||
|
||||
## Configuration
|
||||
|
||||
Environment variables (see `.env.example`):
|
||||
|
||||
| Variable | Default | Meaning |
|
||||
|---------------------|-----------------------------|---------|
|
||||
| `LISTEN_ADDR` | `:8080` | listen address |
|
||||
| `SESSION_TTL` | `12h` | session lifetime |
|
||||
| `COOKIE_SECURE` | `true` | `Secure` cookie flag; use `false` only on plain-http local tests |
|
||||
| `OIDC_ISSUER_URL` | (unset) | e.g. `https://auth.example.com/application/o/yggdrasil/` |
|
||||
| `OIDC_CLIENT_ID` | (unset) | OAuth2 client id |
|
||||
| `OIDC_CLIENT_SECRET`| (unset) | OAuth2 client secret |
|
||||
| `OIDC_REDIRECT_URL` | (unset) | e.g. `https://docs.example.com/callback` |
|
||||
| `OIDC_SCOPES` | `openid profile email groups` | requested scopes |
|
||||
|
||||
Without the `OIDC_*` variables the server runs in public-only mode:
|
||||
public pages are served, protected pages answer 503. The server refuses to
|
||||
start on a malformed `acl.json` or an unreachable OIDC issuer (fail closed).
|
||||
|
||||
### Setting up Authentik
|
||||
|
||||
1. **Applications -> Providers -> Create**: type *OAuth2/OpenID Connect*.
|
||||
2. Authorization flow: *implicit/explicit consent* (default is fine).
|
||||
3. Redirect URI: exactly `https://your-docs-host/callback`.
|
||||
4. Client type: *Confidential*; note the client id and secret.
|
||||
5. **Scope mappings**: make sure the issued ID token contains a `groups`
|
||||
claim. Recent Authentik versions ship a scope mapping that adds
|
||||
`groups`; otherwise create a *Property Mapping (Scope)* with e.g.
|
||||
`return {"groups": [g.name for g in user.ak_groups.all()]}` and add it
|
||||
to the provider.
|
||||
6. Note the issuer URL shown on the provider page
|
||||
(`.../application/o/<slug>/`) and set `OIDC_ISSUER_URL` to it.
|
||||
7. Create groups in **Directory -> Groups** (e.g. `family`, `admin`) whose
|
||||
names match the `access-groups` in your pages, and add users to them.
|
||||
|
||||
The redirect after login returns the user to the page they originally
|
||||
requested. Logout is available at `/logout`.
|
||||
|
||||
## Build and CI
|
||||
|
||||
Local commands (see `Justfile`): `just build`, `just run`, `just parse`,
|
||||
`just check`, `just clean`, `just preview-docs`. Bare `just` prints the
|
||||
command overview. The pinned shiroa lives in `tools/` (gitignored) and is
|
||||
downloaded on demand.
|
||||
|
||||
`.gitea/workflows/build.yml` (Gitea Actions, not GitLab CI) runs on push to
|
||||
`main` and on pull requests: installs shiroa, regenerates `acl.json`,
|
||||
compiles the book, runs `go vet`/`go test`, builds the static binary and
|
||||
uploads it as an artifact.
|
||||
|
||||
A rootless Docker/Podman image is available:
|
||||
|
||||
```sh
|
||||
just parse build-docs
|
||||
docker build -t yggdrasil .
|
||||
docker run --rm -p 8080:8080 --env-file .env yggdrasil
|
||||
```
|
||||
|
||||
Note that the binary embeds the site at build time: publishing new or
|
||||
edited pages always requires rebuilding and redeploying the server.
|
||||
|
||||
## Security notes and limitations
|
||||
|
||||
- Sessions live in memory: they vanish on restart and do not scale past
|
||||
one instance (users simply log in again).
|
||||
- Page *titles* and URLs of protected pages are visible in the sidebar of
|
||||
public pages (that is inherent to a static site). Page *content* is not:
|
||||
neither the HTML nor the renderer data files can be fetched without
|
||||
authorization.
|
||||
- Directory listing is enabled (file names only, e.g. `/thermo/`).
|
||||
- Logout is a GET request (`/logout`), like most documentation sites.
|
||||
- No search is implemented (`searchindex.json` is explicitly blocked).
|
||||
- Static assets referenced by pages (theme CSS/JS, fonts, renderer wasm)
|
||||
are public; they contain no page content.
|
||||
|
||||
## Legacy documentation
|
||||
|
||||
The previous documentation base lives in `legacy_docs/` and is kept only
|
||||
for reference; migrate pages into `docs/` manually, then delete the folder.
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 70 KiB |
@@ -1,13 +0,0 @@
|
||||
# 5G
|
||||
La 5G est l'évolution de le 4G LTE.
|
||||
|
||||
|
||||
## Fonctionnement
|
||||
|
||||
### 5G Standalone VS non-standalone
|
||||

|
||||
|
||||
|
||||
# Liens
|
||||
- [5G NR explications](https://ieeexplore.ieee.org/abstract/document/8879484https://ieeexplore.ieee.org/abstract/document/8879484)
|
||||
- [5G RAN](https://ieeexplore.ieee.org/document/9844937)
|
||||
@@ -1,23 +0,0 @@
|
||||
# C-V2X vs ITS-G5
|
||||
|
||||
# IEEE 802.11p
|
||||
Physical layer :
|
||||
|
||||
Il utilise l'[OFDM](/Wireless-Communication/Protocol/glossary.md#ofdm) pour varier de channel avec deux paramètres : le temps doublé qui implique
|
||||
que la fréquence est divisée de moitié.
|
||||
|
||||
Mac layer :
|
||||
|
||||
Il est basé sur un #OCB operation mode, où on utlisie pas les service d'authentification, d'association et de confidentialité de donnée.
|
||||
Mais, il est bien fait pour émettre de cours messages en broadcast et un au niveau de mobilité.
|
||||
|
||||
# C-V2X
|
||||

|
||||
|
||||
utilise du QPSK ou du 16-QAM
|
||||
|
||||
|
||||
# Conclusion
|
||||
Le C-V2X a de meilleure performance avec le même data rate que le [ITS-G5](/Wireless-Communication/Protocol/glossary.md#its-5g), il possède
|
||||
d'autres paramètres pouvant influencer positivement ses performances. Mais, dès qu'on augmente la densité d'utilisateurs et est supérieur à
|
||||
150 users/km2, le [ITS-G5](/Wireless-Communication/Protocol/glossary.md#its-5g) devient meilleur.
|
||||
@@ -1 +0,0 @@
|
||||
# DECT NR+
|
||||
@@ -1,28 +0,0 @@
|
||||
# IEEE 802.11
|
||||
[IEEE working group](https://www.ieee802.org/11/)
|
||||
|
||||
## 802.11p / ITS-G5
|
||||
|
||||
Cette technologie est la première standardisée pour la communication impliquant des véhicules.
|
||||
|
||||
##* Fonctionnement
|
||||
Elle utilise l'[OFDM](/Wireless-Communication/Protocol/glossary.md#ofdm) :
|
||||
|
||||
Channel Bandwith : 10MHz
|
||||
Modulation : 8
|
||||
|
||||
BitRate : 27Mb/s
|
||||
|
||||
[SCS](/Wireless-Communication/Protocol/glossary.md#scs) : 156.25 kHz
|
||||
|
||||
[CSMA/CA](/Wireless-Communication/Protocol/glossary.md#csmaca)
|
||||
|
||||
Designer pour peu de latence et une grande fiabilité, expressement pour une utilisation sécurisée
|
||||
sur route.
|
||||
|
||||
## IEEE 802.11BD / DOT11BD
|
||||
|
||||
|
||||
|
||||
liens :
|
||||
- [802.11p -> ITS-G5](https://ieeexplore.ieee.org/document/9734746)
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 342 KiB |
@@ -1,11 +0,0 @@
|
||||
<header><h1>
|
||||
Protocol de communication wireless
|
||||
</h1></header>
|
||||
|
||||
- [Glossaire](/Wireless-Communication/Protocol/glossary)
|
||||
- [5G](/Wireless-Communication/Protocol/5g)
|
||||
- [C-V2X vs ITS-G5](/Wireless-Communication/Protocol/C-V2X-vs-ITS-G5)
|
||||
- [IEEE 802.11](/Wireless-Communication/Protocol/IEEE%20802.11)
|
||||
- [V2X](/Wireless-Communication/Protocol/V2X)
|
||||
- [DECT NR+](/Wireless-Communication/Protocol/DECT-NR-+.md)
|
||||
- [Visible light communication](/Wireless-Communication/Protocol/Visible-light-communication.md)
|
||||
@@ -1,41 +0,0 @@
|
||||
# V2X
|
||||
Cette technologie utilise le concept de Sidelink
|
||||
|
||||
## C-V2X
|
||||
|
||||
### LTE-V2X
|
||||
Il est basé sur l'interface sideLink PC5 et la communication cellulaire.
|
||||
|
||||
Il support le broadcast, le groupcast et le unicast.
|
||||
|
||||
#### Mode 3
|
||||
Les [eNB](/Wireless-Communication/Protocol/glossary.md#enb) réservent et planifient les ressources
|
||||
radio pour les véhicules.
|
||||
|
||||
#### Mode 4
|
||||
Les véhicules réservent et planifient les ressource qu'ils ont besoin de manière autonome.
|
||||
|
||||
#### Conclusion
|
||||
Le mode 3 va dépendre de du réseau cellulaire, alors que le 4 est autonome.
|
||||
|
||||
### NR-V2X/5G-V2X
|
||||
|
||||
Il supporte le broadcast, le groupcast et le unicast.
|
||||
|
||||
Il permet d'avoir des sous fréquences porteuses en multiple de 15kHz qui favorise les transmissons
|
||||
critiques.
|
||||
|
||||
#### Mode 1
|
||||
|
||||
#### Mode 2
|
||||
|
||||
### Conclusion
|
||||

|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# Liens
|
||||
|
||||
- [LTE/NR-V2X](https://ieeexplore.ieee.org/document/9734746)
|
||||
@@ -1,10 +0,0 @@
|
||||
# Visible light communication
|
||||
|
||||
La technologie utilise de la lumière visible pour transmettre une information.
|
||||
|
||||
Il y a une application pour véhicule, mais elle s'applique particulièrement bien pour le voiture qui possèdent déjà de
|
||||
phares. Donc, je ne vois pas l'utilité de ce système pour les trains.
|
||||
|
||||
# Liens :
|
||||
- [applications](https://www.sciencedirect.com/science/article/pii/S2352864816300335)
|
||||
-
|
||||
@@ -1,45 +0,0 @@
|
||||
# Global glossary
|
||||
## V2X
|
||||
standard vehicle to vehicle/infrastructure communication systems from 3GPP
|
||||
|
||||
## ITS-5G
|
||||
standard vehicle to vehicle/infrastructure communication systems from IEEE
|
||||
|
||||
## CSMA/CA
|
||||
algo permettant de baisser la probabilité de collision entre packet
|
||||
|
||||
## OFDM
|
||||
Orthogonal Frequency Division Multiplexing
|
||||
|
||||
## OCB
|
||||
Outside the Context of a Basic Service Set
|
||||
|
||||
# C-V2X
|
||||
## RB
|
||||
Ressource Block
|
||||
|
||||
## TB
|
||||
Transport Block
|
||||
|
||||
## PSSCH
|
||||
Physical Sidelink Shared Channels
|
||||
[PSCCH](https://www.ni.com/docs/en-US/bundle/rfmx-waveform-creator/page/v2x-sidelink-communication.html#:~:text=Physical%20sidelink%20control%20channel%20(PSCCH,consecutive%20resource%20blocks%20(RBs).)
|
||||
|
||||
## SCI
|
||||
Sidelink Control Information pour contrôler les informations envoyées
|
||||
|
||||
## SB-SPS
|
||||
Sensing-Based Semi-Persistent Scheduling
|
||||
|
||||
## RC
|
||||
Reselection Counter, c'est ce qui va permettre d'éviter des collisions entre les différents véhicules utilisant les mêmes canaux
|
||||
|
||||
## MSC
|
||||
Modulation and Coding Scheme, détermine comment est transmis les datas (QPSK, ...), parmi la table suivante avec un RE (Resource Element)
|
||||
|
||||
## SCS
|
||||
SubCarrier Spacing, l'espacement entre les fréquences sous-porteuses d'un protocole
|
||||
|
||||
## eNB
|
||||
[Evolved Node B](https://www.artizanetworks.com/resources/tutorials/what_lteenb.html)
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 28 KiB |
@@ -1,18 +0,0 @@
|
||||
# Wireless communication
|
||||
|
||||
## [Protocol](/Wireless-Communication/Protocol/)
|
||||
- [glossaire](/Wireless-Communication/Protocol/glossary)
|
||||
- [C-V2X vs ITS-G5](/Wireless-Communication/Protocol/C-V2X-vs-ITS-G5.md)
|
||||
- [IEEE 802.11](/Wireless-Communication/Protocol/IEEE%20802.11)
|
||||
- [V2X](/Wireless-Communication/Protocol/V2X)
|
||||
- [DECT NR+](/Wireless-Communication/Protocol/DECT-NR-+.md)
|
||||
- [Visible light communication](/Wireless-Communication/Protocol/Visible-light-communication.md)
|
||||
|
||||
|
||||
## [Train](/Wireless-Communication/Train/)
|
||||
- [Glossaire](/Wireless-Communication/Train/glossary.md)
|
||||
- [Channel model for performance evaluation](/Wireless-Communication/Train/Channel-model-for-performance-evaluation.md)
|
||||
- [FRMC - Projet 5GRail](/Wireless-Communication/Train/FRMC-Projet-5GRail.md)
|
||||
- [WTCMS](/Wireless-Communication/Train/WTCMS.md)
|
||||
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
# Channel model for performance evaluation
|
||||
|
||||
## Tapped delay line
|
||||
|
||||

|
||||
|
||||
Il faut imaginer qu'après un élément de retard on vient rajouter un le signal retarder et on devrait obtenir en sortie une
|
||||
sorte d'escalier si on regarde assez petit. Cela, est ma réfléction en regardant le graphe au-dessus.
|
||||
|
||||
Il permet de simuler une communication sans file en simulant les pilonnes qui relayent le signal avec un retard
|
||||
s'additionnant au signal de base. Ensuite, il ne faut pas oublier qu'un effet doppler rentre en ligne de compte à chaque élément
|
||||
s'ajoutant sur le chemin de la transmission entre pilonnes.
|
||||
|
||||
## Environnement des chemins de fer
|
||||
Il faut prendre en compte la vitesse, le lieu (tunnel pour les métro, pilonne, ...)
|
||||
|
||||
On trouve 3 scénarios de communications :
|
||||
- Train-to-Ground (T2G)
|
||||
- Train-to-Train (T2T)
|
||||
- intra-train communications
|
||||
|
||||
## Conclusion
|
||||
Le papier montre qu'il existe plein de modèle de canaux, mais il ne prenne pas en compte tous les scénarios. Seulement les suivants :
|
||||
- Montagneux
|
||||
- Rural
|
||||
- Viaduque
|
||||
- de coupure
|
||||
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
# FRMC Projet 5GRail
|
||||
Le document décrit comment le projet 5GRail a été prévu et simulé. Le but est de réaliser une simulation end-to-end pour
|
||||
plusieurs services utilisé par les trains (les fournisseurs). Le tout est séparé en plusieurs workpackage :
|
||||
|
||||
WP1 : dirigé par International Union of Railways (UIC)
|
||||
|
||||
WP2 : OBapp (on board applications) et TSapp (TrackSide application)
|
||||
|
||||
WP3 : fait au laboratoire nokia à Budapest
|
||||
- 5G/FRMCS-based [MCPTT](/Wireless-Communication/Train/glossary.md#mcptt) point-to-point and group voice communication realized by Siemens,
|
||||
- 5G/FRMCS-based Railway Emergency Call (REC) realized by Siemens,
|
||||
- [ETCS](/Wireless-Communication/Train/glossary.md#etcs) realized by CAF,
|
||||
- Train Control and Monitoring System (TCMS) realized by CAF,
|
||||
- Non-critical video application and CCTV archive transfer realized by Teleste.
|
||||
|
||||
WP4 : liaison des sites via VPN pour les testes
|
||||
|
||||
WP5 : testes réels
|
||||
|
||||
Fonctionnement :
|
||||
Le communication se fait au moyen de gateways qui permettent d'envoyer les données entre les trains et les stations de contrôle
|
||||
|
||||
Résultats des essais en laboratoires :
|
||||
- le premier appel end-to-end FRMCS
|
||||
- le management de la qualité des services en utilisant la méthode [DSCP](/Wireless-Communication/Train/glossary.md#DSCP)
|
||||
- les scénarios de changement de réseau 5G avec le [RV](/Wireless-Communication/Train/glossary.md#RV) activé
|
||||
- Préparations des activités pour les tests du package 5 c-à-d, **les effet de la vitesse sur les fréquence radios**
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
<header><h1>
|
||||
Protocol de communication wireless
|
||||
</h1></header>
|
||||
|
||||
- [Glossaire](/Wireless-Communication/Train/glossary)
|
||||
- [Channel model for performance evaluation](/Wireless-Communication/Train/Channel-model-for-performance-evaluation)
|
||||
- [FRMC - Projet 5GRail](/Wireless-Communication/Train/FRMC-Projet-5GRail)
|
||||
- [WTCMS](/Wireless-Communication/Train/WTCMS)
|
||||
@@ -1,50 +0,0 @@
|
||||
# Introduction
|
||||
Les trains utilise la technologie [GSM-R](/Wireless-Communication/Train/glossary.md#gsm-r) qui est
|
||||
l'ancien réseau mobile des trains. Dans notre cas, on veux utiliser la technologie
|
||||
[WTCMS](/Wireless-Communication/Train/glossary.md#wtcms).
|
||||
|
||||
# WTCMS
|
||||
Cette nouvelle technologie doit permettre d'enlever [ETB](/Wireless-Communication/Train/glossary.md#etb).
|
||||
|
||||
## Prérequis
|
||||
|
||||
1. la latence ne dois pas excéder 3 fois le temps de cycle du train
|
||||
2. le trains peut avoir 32 wagon avec une distance totale de 850m.
|
||||
3. Il doit être capable de d'interconnecter jsuqu'à 32 nodes
|
||||
4. Le changement dois être invisible pour les end-users
|
||||
|
||||
## Topologies alternatives
|
||||
### Variant 1 : Topologie linéaire
|
||||
Chacun des noeuds pour parler directement à ses voisins. Il peut communiquer avec le reste du réseau aussi en passant
|
||||
par ses voisins.
|
||||
|
||||
Cette manière permet d'atlerner les fréquences de communications entre chaque wagon. De cette manière, on utilise un moins
|
||||
large spectre dans les fréquences.
|
||||
|
||||
Son défaut est la latence total.
|
||||
Par contre, ce serait bien plus facile de mettre en place. Puisque le système est similaire à ce qui est déjà en place.
|
||||
Mais, la solution supprime pas tellement le réseau filaire actuel.
|
||||
|
||||
### Variant 2a : Topologie en Mesh
|
||||
Dans cette variante la topologie mesh permet de communiquer directement avec n'importe quel autre noeud du réseau. Mais,
|
||||
la latence dépend de la vitesse à laquelle chaque noeud peut réceptionner et transmettre.
|
||||
|
||||
### Variant 2b : Topologie en Mesh
|
||||
Dans cette variante, Il utilise la [Variant 1](#variant-1-topologie-linéaire) améliorée. C'est à dire, les noeuds peuvent
|
||||
communique transmette à leur voisin, s'ils ne peuvent pas atteindre le noeud cible.
|
||||
|
||||
### Conclusion
|
||||
La variant 1 est la plus simple à mettre en place. Mais, il garde la majorité des fils et garde une latence proportionnelle
|
||||
au chemin à parcourir du packet.
|
||||
|
||||
La variant 2a supprime le problème de délai de la variante 1, mais ce n'est pas réaliste de mettre une antenne pour émettre
|
||||
à 850m sur chaque wagon.
|
||||
|
||||
La variante 2b réduit la latence de la variante 1, en gardant des antennes qui emettent à 250m.
|
||||
|
||||
Finalement, la variante 2b est la plus réaliste à mettre en place.
|
||||
|
||||
## Comparaison de différent protocole
|
||||

|
||||
|
||||
|
||||
@@ -1,101 +0,0 @@
|
||||
# Global glossary
|
||||
|
||||
## CDL
|
||||
Cluster-Delay-Line
|
||||
|
||||
## CIR
|
||||
Channel Impulse Response
|
||||
|
||||
## LoS
|
||||
Line of Sight, le chemin direct entre un émetteur et un récepteur
|
||||
|
||||
## NLOS
|
||||
Non-Line-Of-Sight
|
||||
|
||||
## CRS
|
||||
cell-specific reference signal
|
||||
|
||||
## IP
|
||||
Intersection Point, le point d'intercection entre les rails et le le pilonne de transmission
|
||||
|
||||
## UL
|
||||
Uplink, c'est la cummunication entre le qqch au sol et le satellite
|
||||
|
||||
## DL
|
||||
Downlink, c'est la communication entre le satellite et le qqch au sol
|
||||
|
||||
## OAI
|
||||
Open Air Interface, c'est un projet open source pour les réseaux cellulaires RAN (Radio Access Network) et CN (Core Network)
|
||||
|
||||
## FRMCS
|
||||
Future Railway Mobile Communication System
|
||||
|
||||
## DSCP
|
||||
permet de classer le packet dans un réseau IP par ordre de priorité
|
||||
|
||||
## ETCS
|
||||
European Train Control System
|
||||
|
||||
## ATO
|
||||
Automatic Train Operation
|
||||
|
||||
## RV
|
||||
Remote Vision
|
||||
|
||||
## 5QI
|
||||
standard pour le 5G QoS identifiant, pour savoir la qualité du service
|
||||
|
||||
## MCPTT
|
||||
Mission Critical Push-to-Talk (utilisé pour la communication critique avec la pression sur une touche pour parler simplement et rapidement)
|
||||
|
||||
## PIS
|
||||
Passenger Information System
|
||||
|
||||
## RMR
|
||||
Railway Mobile Radio, réseau de communication pour les trains
|
||||
|
||||
## TDL
|
||||
Tapped-Delay-Line
|
||||
|
||||
## Jitter
|
||||
la variance de la latence entre le moment de la transmission et le moment de la réception
|
||||
|
||||
## RAT
|
||||
Radio Access Technology, la technologie utilisée pour accéder au réseau
|
||||
|
||||
## TCMS
|
||||
Train Control and Management System
|
||||
|
||||
## WTCMS
|
||||
Wireless Train Control and Management System
|
||||
|
||||
## GSM-R
|
||||
Global System for Mobile Communications - Railway, c'est un standard ancien de communication pour les trains
|
||||
|
||||
## WLTB
|
||||
Wireless Local Train Backbone, c'est un réseau de communication sans fil pour les trains
|
||||
|
||||
## ETB
|
||||
Ethernet Train Backbone, c'est un réseau de communication basé sur Ethernet pour les trains qui va être remplacer par le WLTB
|
||||
|
||||
## ETBN
|
||||
Ethernet Train Backbone Network, c'est un réseau de communication basé sur Ethernet pour les trains qui va être remplacer par le WLTB
|
||||
|
||||
## OMTS
|
||||
On-board Multimedia and Telematics
|
||||
|
||||
## COTS
|
||||
Commercial Off-The-Shelf, comprend l'ensemble des produits pouvant être utilisé achetable dans le commerce
|
||||
|
||||
## NG-TCN
|
||||
Next Generation Train Control Network,
|
||||
|
||||
## TSM
|
||||
Time Sensitve Networking, fonctionnalités qui servent à garantir les services dans le temps, déterministe
|
||||
|
||||
## Consist
|
||||
un ensemble de wagons qui sont attachés ensemble pour former par exemple une série de wagon 2ième classe ou autre chose
|
||||
|
||||
|
||||
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 142 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 5.4 KiB |
@@ -1,13 +0,0 @@
|
||||
* [Protocol](/Wireless-Communication/Protocol/)
|
||||
* [glossaire](/Wireless-Communication/Protocol/glossary)
|
||||
* [C-V2X vs ITS-G5](/Wireless-Communication/Protocol/C-V2X-vs-ITS-G5)
|
||||
* [IEEE 802.11](/Wireless-Communication/Protocol/IEEE%20802.11)
|
||||
* [V2X](/Wireless-Communication/Protocol/V2X)
|
||||
* [DECT NR+](/Wireless-Communication/Protocol/DECT-NR-+.md)
|
||||
* [Visible light communication](/Wireless-Communication/Protocol/Visible-light-communication.md)
|
||||
|
||||
* [Train](/Wireless-Communication/Train/)
|
||||
* [glossaire](/Wireless-Communication/Train/glossary)
|
||||
* [Channel model for performance evaluation](/Wireless-Communication/Train/Channel-model-for-performance-evaluation)
|
||||
* [FRMC - Projet 5GRail](/Wireless-Communication/Train/FRMC-Projet-5GRail)
|
||||
* [WTCMS](/Wireless-Communication/Train/WTCMS)
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"/homelab/backup.html": [
|
||||
"family"
|
||||
],
|
||||
"/security/ssh.html": [
|
||||
"admin"
|
||||
],
|
||||
"/thermo/fluids.html": [
|
||||
"family",
|
||||
"admin"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
// Command parser scans a directory of Typst sources (a shiroa book) for
|
||||
// access-group tags and generates the acl.json consumed by the server.
|
||||
//
|
||||
// A page declares its access groups with a Typst let binding at the top of
|
||||
// the file:
|
||||
//
|
||||
// #let access-groups = ("family", "admin")
|
||||
//
|
||||
// Pages without a tag are public. The generated acl.json maps the final URL
|
||||
// of each page (e.g. "/thermo/fluids.html") to its authorized groups.
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Matches: #let access-groups = ("group1", "group2")
|
||||
var accessGroupsRe = regexp.MustCompile(`(?m)^\s*#let\s+access-groups\s*=\s*\(([^)]*)\)`)
|
||||
|
||||
// Files that are never pages: book metadata, page layout, and partials
|
||||
// (files starting with "_") imported by other pages.
|
||||
func isSkipped(base string) bool {
|
||||
return base == "book.typ" || base == "template.typ" || strings.HasPrefix(base, "_")
|
||||
}
|
||||
|
||||
func main() {
|
||||
inDir := flag.String("in", "docs", "input directory containing the shiroa book (.typ sources)")
|
||||
outFile := flag.String("out", "acl.json", "output acl.json file")
|
||||
flag.Parse()
|
||||
|
||||
acl := make(map[string][]string)
|
||||
|
||||
err := filepath.WalkDir(*inDir, func(p string, d fs.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
base := d.Name()
|
||||
if d.IsDir() {
|
||||
// Skip build output and hidden directories.
|
||||
if p != *inDir && (base == "dist" || strings.HasPrefix(base, ".")) {
|
||||
return filepath.SkipDir
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if filepath.Ext(base) != ".typ" || isSkipped(base) {
|
||||
return nil
|
||||
}
|
||||
|
||||
content, err := os.ReadFile(p)
|
||||
if err != nil {
|
||||
return fmt.Errorf("reading %s: %w", p, err)
|
||||
}
|
||||
groups := extractGroups(content)
|
||||
if len(groups) == 0 {
|
||||
return nil // public page, no rule needed
|
||||
}
|
||||
|
||||
relPath, err := filepath.Rel(*inDir, p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// docs/<path>.typ compiles to dist/<path>.html, served at /<path>.html
|
||||
urlPath := "/" + strings.TrimSuffix(filepath.ToSlash(relPath), ".typ") + ".html"
|
||||
|
||||
acl[urlPath] = groups
|
||||
log.Printf("protected %s -> %s", urlPath, strings.Join(groups, ", "))
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatalf("scanning %s: %v", *inDir, err)
|
||||
}
|
||||
|
||||
out, err := json.MarshalIndent(acl, "", " ")
|
||||
if err != nil {
|
||||
log.Fatalf("encoding acl: %v", err)
|
||||
}
|
||||
out = append(out, '\n')
|
||||
if err := os.WriteFile(*outFile, out, 0o644); err != nil {
|
||||
log.Fatalf("writing %s: %v", *outFile, err)
|
||||
}
|
||||
|
||||
fmt.Printf("generated %s (%d protected pages)\n", *outFile, len(acl))
|
||||
}
|
||||
|
||||
func extractGroups(content []byte) []string {
|
||||
m := accessGroupsRe.FindSubmatch(content)
|
||||
if m == nil {
|
||||
return nil
|
||||
}
|
||||
var groups []string
|
||||
for _, part := range strings.Split(string(m[1]), ",") {
|
||||
part = strings.TrimSpace(part)
|
||||
part = strings.Trim(part, "\"'")
|
||||
if part != "" {
|
||||
groups = append(groups, part)
|
||||
}
|
||||
}
|
||||
return groups
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
// Command server is the Yggdrasil web server: a single static binary that
|
||||
// embeds the compiled documentation (dist/) and its access control list
|
||||
// (acl.json), serves public pages directly, and gates protected pages
|
||||
// behind OIDC authentication (Authentik).
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/Klagarge/yggdrasil"
|
||||
"github.com/Klagarge/yggdrasil/internal/acl"
|
||||
"github.com/Klagarge/yggdrasil/internal/middleware"
|
||||
"github.com/Klagarge/yggdrasil/internal/oidc"
|
||||
)
|
||||
|
||||
func main() {
|
||||
listenAddr := envOr("LISTEN_ADDR", ":8080")
|
||||
sessionTTL := envDuration("SESSION_TTL", 12*time.Hour)
|
||||
cookieSecure := envOr("COOKIE_SECURE", "true") == "true"
|
||||
|
||||
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
|
||||
defer stop()
|
||||
|
||||
list, err := acl.Load(yggdrasil.ACLData())
|
||||
if err != nil {
|
||||
log.Fatalf("refusing to start: %v", err) // fail closed
|
||||
}
|
||||
|
||||
sessions := middleware.NewSessionStore(sessionTTL)
|
||||
go sessions.StartJanitor(ctx, 10*time.Minute)
|
||||
|
||||
auth := setupOIDC(ctx)
|
||||
|
||||
dist, err := yggdrasil.DistFS()
|
||||
if err != nil {
|
||||
log.Fatalf("embedded dist: %v", err)
|
||||
}
|
||||
|
||||
gate := middleware.NewAccessControl(list, sessions, auth, cookieSecure)
|
||||
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("GET /login", gate.Login)
|
||||
mux.HandleFunc("GET /callback", gate.Callback)
|
||||
mux.HandleFunc("GET /logout", gate.Logout)
|
||||
mux.HandleFunc("GET /healthz", func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
_, _ = w.Write([]byte("ok\n"))
|
||||
})
|
||||
// The search index contains the text of protected pages: never serve it.
|
||||
mux.HandleFunc("/searchindex.json", http.NotFound)
|
||||
mux.Handle("/", gate.Wrap(http.FileServerFS(dist)))
|
||||
|
||||
srv := &http.Server{
|
||||
Addr: listenAddr,
|
||||
Handler: gate.SecurityHeaders(mux),
|
||||
ReadHeaderTimeout: 10 * time.Second,
|
||||
ReadTimeout: 30 * time.Second,
|
||||
WriteTimeout: 60 * time.Second,
|
||||
IdleTimeout: 2 * time.Minute,
|
||||
}
|
||||
|
||||
errCh := make(chan error, 1)
|
||||
go func() { errCh <- srv.ListenAndServe() }()
|
||||
log.Printf("listening on %s (%d protected pages)", listenAddr, len(list))
|
||||
|
||||
select {
|
||||
case err := <-errCh:
|
||||
if !errors.Is(err, http.ErrServerClosed) {
|
||||
log.Fatalf("server error: %v", err)
|
||||
}
|
||||
case <-ctx.Done():
|
||||
}
|
||||
|
||||
shutdownCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
if err := srv.Shutdown(shutdownCtx); err != nil {
|
||||
log.Printf("shutdown: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// setupOIDC builds the OIDC client from the environment. Without OIDC
|
||||
// configuration the server still serves public pages; protected pages
|
||||
// answer 503 instead of being exposed.
|
||||
func setupOIDC(ctx context.Context) *oidc.Authenticator {
|
||||
cfg, err := oidc.LoadConfigFromEnv()
|
||||
if err != nil {
|
||||
log.Printf("OIDC disabled (%v) - protected pages will answer 503", err)
|
||||
return nil
|
||||
}
|
||||
auth, err := oidc.NewAuthenticator(ctx, cfg)
|
||||
if err != nil {
|
||||
log.Fatalf("OIDC setup: %v", err)
|
||||
}
|
||||
log.Printf("OIDC enabled (issuer: %s)", cfg.IssuerURL)
|
||||
return auth
|
||||
}
|
||||
|
||||
func envOr(key, def string) string {
|
||||
if v := os.Getenv(key); v != "" {
|
||||
return v
|
||||
}
|
||||
return def
|
||||
}
|
||||
|
||||
func envDuration(key string, def time.Duration) time.Duration {
|
||||
if v := os.Getenv(key); v != "" {
|
||||
if d, err := time.ParseDuration(v); err == nil {
|
||||
return d
|
||||
}
|
||||
log.Printf("invalid %s %q, using %s", key, v, def)
|
||||
}
|
||||
return def
|
||||
}
|
||||
Vendored
File renamed without changes.
@@ -0,0 +1,22 @@
|
||||
#import "@preview/shiroa:0.4.0": *
|
||||
#show: book
|
||||
|
||||
#book-meta(
|
||||
title: "Yggdrasil",
|
||||
authors: ("Klagarge",),
|
||||
summary: [
|
||||
#prefix-chapter("index.typ")[Home]
|
||||
|
||||
= Thermodynamics
|
||||
- #chapter("thermo/heat-pumps.typ", section: "1")[Heat pumps]
|
||||
- #chapter("thermo/fluids.typ", section: "2")[Fluid mechanics]
|
||||
|
||||
= Security
|
||||
- #chapter("security/opsec.typ", section: "3")[Opsec basics]
|
||||
- #chapter("security/ssh.typ", section: "4")[SSH hardening]
|
||||
|
||||
= Homelab
|
||||
- #chapter("homelab/self-hosting.typ", section: "5")[Self-hosting]
|
||||
- #chapter("homelab/backup.typ", section: "6")[Backups]
|
||||
],
|
||||
)
|
||||
@@ -0,0 +1,11 @@
|
||||
// Access control: only members of these Authentik groups can read this page.
|
||||
// Delete this line to make the page public.
|
||||
#let access-groups = ("family",)
|
||||
|
||||
#import "../template.typ": project
|
||||
#show: project
|
||||
|
||||
= Backups
|
||||
|
||||
Protected page placeholder about backups: the 3-2-1 rule, restic,
|
||||
off-site replication...
|
||||
@@ -0,0 +1,7 @@
|
||||
#import "../template.typ": project
|
||||
#show: project
|
||||
|
||||
= Self-hosting
|
||||
|
||||
Public page placeholder about self-hosting: Docker/Podman, reverse
|
||||
proxies, DNS, VLANs...
|
||||
@@ -0,0 +1,17 @@
|
||||
#import "template.typ": project
|
||||
#show: project
|
||||
|
||||
= Yggdrasil
|
||||
|
||||
Welcome to the Yggdrasil knowledge base, built with Typst, Shiroa and a Go
|
||||
server.
|
||||
|
||||
*How it works:*
|
||||
|
||||
- Pages are written in Typst under `docs/` and compiled to static HTML.
|
||||
- A Go server embeds the HTML and an access-control list (`acl.json`).
|
||||
- Pages tagged with `#let access-groups = ("family", "admin")` are only
|
||||
visible to authenticated users in those groups; everything else is
|
||||
public.
|
||||
|
||||
Browse the categories in the sidebar to get started.
|
||||
@@ -0,0 +1,7 @@
|
||||
#import "../template.typ": project
|
||||
#show: project
|
||||
|
||||
= Opsec basics
|
||||
|
||||
Public page placeholder about operational security: threat models,
|
||||
password managers, 2FA...
|
||||
@@ -0,0 +1,11 @@
|
||||
// Access control: only members of these Authentik groups can read this page.
|
||||
// Delete this line to make the page public.
|
||||
#let access-groups = ("admin",)
|
||||
|
||||
#import "../template.typ": project
|
||||
#show: project
|
||||
|
||||
= SSH hardening
|
||||
|
||||
Protected page placeholder about SSH: key management, ciphers,
|
||||
session hardening, bastion hosts...
|
||||
@@ -0,0 +1,44 @@
|
||||
#import "@preview/shiroa:0.4.0": get-page-width, is-html-target, is-pdf-target, is-web-target, templates, x-target
|
||||
#import templates: *
|
||||
|
||||
// Metadata
|
||||
#let page-width = get-page-width()
|
||||
#let is-html-target = is-html-target() // target.starts-with("html")
|
||||
#let is-pdf-target = is-pdf-target() // target.starts-with("pdf")
|
||||
#let is-web-target = is-web-target() // target.starts-with("web")
|
||||
|
||||
#let project(body) = {
|
||||
// set web/pdf page properties
|
||||
set page(
|
||||
width: page-width,
|
||||
// for a website, we don't need pagination.
|
||||
height: auto,
|
||||
) if is-pdf-target or is-web-target
|
||||
|
||||
// remove margins for web target
|
||||
set page(margin: (
|
||||
// reserved beautiful top margin
|
||||
top: 20pt,
|
||||
// Typst is setting the page's bottom to the baseline of the last line of text. So bad :(.
|
||||
bottom: 0.5em,
|
||||
// remove rest margins.
|
||||
rest: 0pt,
|
||||
)) if is-web-target
|
||||
|
||||
let web-theme = if x-target.starts-with("html") and not x-target.starts-with("html-wrapper") {
|
||||
"starlight"
|
||||
} else {
|
||||
"mdbook"
|
||||
}
|
||||
show: if web-theme == "starlight" {
|
||||
import "@preview/shiroa-starlight:0.4.0": starlight
|
||||
starlight.with(include "book.typ")
|
||||
} else if web-theme == "mdbook" {
|
||||
import "@preview/shiroa-mdbook:0.4.0": mdbook
|
||||
mdbook.with(include "book.typ")
|
||||
} else {
|
||||
panic("Unknown web theme: " + web-theme)
|
||||
}
|
||||
|
||||
body
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
// Access control: only members of these Authentik groups can read this page.
|
||||
// Delete this line to make the page public.
|
||||
#let access-groups = ("family", "admin")
|
||||
|
||||
#import "../template.typ": project
|
||||
#show: project
|
||||
|
||||
= Fluid mechanics
|
||||
|
||||
Protected page placeholder about fluid mechanics: statics, dynamics,
|
||||
Bernoulli's principle, pressure losses...
|
||||
@@ -0,0 +1,7 @@
|
||||
#import "../template.typ": project
|
||||
#show: project
|
||||
|
||||
= Heat pumps
|
||||
|
||||
Public page placeholder about heat pumps: COP, seasonal performance,
|
||||
refrigerant cycles...
|
||||
@@ -0,0 +1,26 @@
|
||||
// Package yggdrasil embeds the build artifacts: the static site compiled by
|
||||
// shiroa (dist/) and the access control list (acl.json). Embedding them at
|
||||
// the repository root lets //go:embed see both directories relative to this
|
||||
// file; `go build` requires acl.json and dist/ to exist (see the Justfile).
|
||||
package yggdrasil
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"io/fs"
|
||||
)
|
||||
|
||||
//go:embed all:dist
|
||||
var distFS embed.FS
|
||||
|
||||
//go:embed acl.json
|
||||
var aclData []byte
|
||||
|
||||
// DistFS returns the embedded static site.
|
||||
func DistFS() (fs.FS, error) {
|
||||
return fs.Sub(distFS, "dist")
|
||||
}
|
||||
|
||||
// ACLData returns the embedded acl.json.
|
||||
func ACLData() []byte {
|
||||
return aclData
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
module github.com/Klagarge/yggdrasil
|
||||
|
||||
go 1.27.1
|
||||
|
||||
require (
|
||||
github.com/coreos/go-oidc/v3 v3.21.0
|
||||
golang.org/x/oauth2 v0.37.0
|
||||
)
|
||||
|
||||
require github.com/go-jose/go-jose/v4 v4.1.4 // indirect
|
||||
@@ -0,0 +1,6 @@
|
||||
github.com/coreos/go-oidc/v3 v3.21.0 h1:wZo4Q9Pum8dYEj0eMUPrqR+kvuGkeUplbLpNCkBqoWM=
|
||||
github.com/coreos/go-oidc/v3 v3.21.0/go.mod h1:DYCf24+ncYi+XkIH97GY1+dqoRlbaSI26KVTCI9SrY4=
|
||||
github.com/go-jose/go-jose/v4 v4.1.4 h1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA=
|
||||
github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
|
||||
golang.org/x/oauth2 v0.37.0 h1:JUlcxA8oAtauLfiH8FX2/FkAWHAdi0QtGCGc+hofE98=
|
||||
golang.org/x/oauth2 v0.37.0/go.mod h1:IxwZNxUULJmpBFf9K/9NTMSIfZZuvuTy1gGxhigP/58=
|
||||
@@ -0,0 +1,122 @@
|
||||
// Package acl loads the access control list generated by cmd/parser and
|
||||
// answers authorization questions for the HTTP middleware.
|
||||
//
|
||||
// acl.json maps the URL of each protected page ("/thermo/fluids.html") to
|
||||
// the groups allowed to read it. URLs absent from the list are public.
|
||||
//
|
||||
// Keys are normalized to a "page key": the URL path reduced to the page it
|
||||
// belongs to, e.g. "/thermo/fluids.html" and "/thermo/fluids.html" both
|
||||
// become "/thermo/fluids". The shiroa renderer fetches the page content
|
||||
// from companion files such as "/thermo/fluids.light.multi.sir.in", which
|
||||
// reduce to the same key, so a protected page's content cannot be fetched
|
||||
// directly while the page itself is protected.
|
||||
package acl
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"path"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ACL maps page keys ("/thermo/fluids") to the groups allowed to read them.
|
||||
type ACL map[string][]string
|
||||
|
||||
// Load parses acl.json and normalizes its keys. A malformed file is an error:
|
||||
// the caller must refuse to start rather than serve protected pages as
|
||||
// public (fail closed).
|
||||
func Load(data []byte) (ACL, error) {
|
||||
var raw map[string][]string
|
||||
if err := json.Unmarshal(data, &raw); err != nil {
|
||||
return nil, fmt.Errorf("parsing acl.json: %w", err)
|
||||
}
|
||||
acl := make(ACL, len(raw))
|
||||
for urlPath, groups := range raw {
|
||||
if len(groups) == 0 {
|
||||
continue
|
||||
}
|
||||
key, err := PageKey(urlPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("acl entry %q: %w", urlPath, err)
|
||||
}
|
||||
// Merge entries that reduce to the same page (e.g. a page and
|
||||
// its directory index): require the union of their groups.
|
||||
acl[key] = appendUnique(acl[key], groups...)
|
||||
}
|
||||
return acl, nil
|
||||
}
|
||||
|
||||
// PageKey reduces a URL path to the page it belongs to.
|
||||
//
|
||||
// "/thermo/fluids.html" -> "/thermo/fluids"
|
||||
// "/thermo/fluids.light.multi.sir.in" -> "/thermo/fluids"
|
||||
// "/" -> "/index"
|
||||
// "/thermo/" -> "/thermo/index"
|
||||
func PageKey(urlPath string) (string, error) {
|
||||
if !strings.HasPrefix(urlPath, "/") {
|
||||
return "", fmt.Errorf("not an absolute path")
|
||||
}
|
||||
// A request for a directory serves that directory's index page.
|
||||
if urlPath != "/" && strings.HasSuffix(urlPath, "/") {
|
||||
urlPath += "index.html"
|
||||
}
|
||||
p := path.Clean(urlPath)
|
||||
if p == "/" {
|
||||
return "/index", nil
|
||||
}
|
||||
dir, base := path.Split(p)
|
||||
if i := strings.Index(base, "."); i >= 0 {
|
||||
base = base[:i]
|
||||
}
|
||||
if base == "" {
|
||||
base = "index"
|
||||
}
|
||||
return path.Join(dir, base), nil
|
||||
}
|
||||
|
||||
// GroupsFor returns the union of the groups required by any of the given
|
||||
// page keys, and whether at least one of them is protected.
|
||||
func (a ACL) GroupsFor(keys ...string) (required []string, protected bool) {
|
||||
seen := make(map[string]struct{})
|
||||
for _, k := range keys {
|
||||
for _, g := range a[k] {
|
||||
if _, dup := seen[g]; !dup {
|
||||
seen[g] = struct{}{}
|
||||
required = append(required, g)
|
||||
}
|
||||
protected = true
|
||||
}
|
||||
}
|
||||
return required, protected
|
||||
}
|
||||
|
||||
// Allows reports whether the user's groups include at least one of the
|
||||
// required groups.
|
||||
func Allows(userGroups, required []string) bool {
|
||||
for _, u := range userGroups {
|
||||
for _, r := range required {
|
||||
if u == r {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func appendUnique(dst []string, groups ...string) []string {
|
||||
for _, g := range groups {
|
||||
if !contains(dst, g) {
|
||||
dst = append(dst, g)
|
||||
}
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
func contains(list []string, s string) bool {
|
||||
for _, x := range list {
|
||||
if x == s {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package acl
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestPageKey(t *testing.T) {
|
||||
cases := []struct{ in, want string }{
|
||||
{"/", "/index"},
|
||||
{"/index.html", "/index"},
|
||||
{"/thermo/fluids.html", "/thermo/fluids"},
|
||||
// The renderer fetches content from theme variants; they must map
|
||||
// to the same page or the ACL would be bypassable.
|
||||
{"/thermo/fluids.light.multi.sir.in", "/thermo/fluids"},
|
||||
{"/thermo/fluids.ayu.multi.sir.in", "/thermo/fluids"},
|
||||
// Directory requests resolve to the directory index page.
|
||||
{"/thermo/", "/thermo/index"},
|
||||
{"/thermo", "/thermo"},
|
||||
// Path traversal is neutralized before matching.
|
||||
{"/thermo/../security/ssh.html", "/security/ssh"},
|
||||
}
|
||||
for _, c := range cases {
|
||||
if got, err := PageKey(c.in); err != nil {
|
||||
t.Errorf("PageKey(%q) error: %v", c.in, err)
|
||||
} else if got != c.want {
|
||||
t.Errorf("PageKey(%q) = %q, want %q", c.in, got, c.want)
|
||||
}
|
||||
}
|
||||
if _, err := PageKey("relative.html"); err == nil {
|
||||
t.Error("PageKey must reject relative paths")
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadAndCheck(t *testing.T) {
|
||||
// Keys in acl.json are final page URLs; Load normalizes them.
|
||||
data := []byte(`{
|
||||
"/thermo/fluids.html": ["family", "admin"],
|
||||
"/index.html": ["admin"],
|
||||
"/public.html": ["family"]
|
||||
}`)
|
||||
a, err := Load(data)
|
||||
if err != nil {
|
||||
t.Fatalf("Load: %v", err)
|
||||
}
|
||||
if got, _ := a.GroupsFor("/thermo/fluids"); len(got) != 2 {
|
||||
t.Errorf("expected 2 groups for /thermo/fluids, got %v", got)
|
||||
}
|
||||
if _, protected := a.GroupsFor("/thermo/fluids"); !protected {
|
||||
t.Error("/thermo/fluids should be protected")
|
||||
}
|
||||
if _, protected := a.GroupsFor("/thermo/heat-pumps"); protected {
|
||||
t.Error("/thermo/heat-pumps should be public")
|
||||
}
|
||||
if !Allows([]string{"family"}, []string{"family", "admin"}) {
|
||||
t.Error("family should be allowed")
|
||||
}
|
||||
if Allows([]string{"guest"}, []string{"family", "admin"}) {
|
||||
t.Error("guest should be denied")
|
||||
}
|
||||
if Allows(nil, []string{"admin"}) {
|
||||
t.Error("no groups should never be allowed on a protected page")
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadMalformed(t *testing.T) {
|
||||
if _, err := Load([]byte("not json")); err == nil {
|
||||
t.Fatal("malformed acl.json must be an error (fail closed)")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
// Package middleware implements the access-control layer of the server:
|
||||
// OIDC login/logout, cookie sessions, and the gate that decides for every
|
||||
// request whether its URL may be served to the caller.
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/Klagarge/yggdrasil/internal/acl"
|
||||
"github.com/Klagarge/yggdrasil/internal/oidc"
|
||||
)
|
||||
|
||||
const (
|
||||
sessionCookie = "ygg_session"
|
||||
stateTTL = 10 * time.Minute
|
||||
)
|
||||
|
||||
// AccessControl bundles everything the gate needs.
|
||||
type AccessControl struct {
|
||||
ACL acl.ACL
|
||||
Sessions *SessionStore
|
||||
Auth *oidc.Authenticator // nil: OIDC disabled
|
||||
CookieSecure bool
|
||||
|
||||
stateMu sync.Mutex
|
||||
states map[string]loginState // pending OIDC flows, keyed by state
|
||||
}
|
||||
|
||||
// NewAccessControl wires the gate. auth may be nil (OIDC disabled): public
|
||||
// pages keep working and protected pages answer 503.
|
||||
func NewAccessControl(a acl.ACL, sessions *SessionStore, auth *oidc.Authenticator, cookieSecure bool) *AccessControl {
|
||||
return &AccessControl{
|
||||
ACL: a,
|
||||
Sessions: sessions,
|
||||
Auth: auth,
|
||||
CookieSecure: cookieSecure,
|
||||
states: make(map[string]loginState),
|
||||
}
|
||||
}
|
||||
|
||||
// Wrap returns a handler that serves next only when the request is allowed.
|
||||
//
|
||||
// The request path is reduced to a page key (see acl.PageKey) and looked up
|
||||
// in the ACL. Public pages pass through; protected pages require a session
|
||||
// whose groups include at least one of the groups required by the ACL.
|
||||
func (ac *AccessControl) Wrap(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
key, err := acl.PageKey(r.URL.Path)
|
||||
if err != nil {
|
||||
http.Error(w, "invalid request path", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
required, protected := ac.ACL.GroupsFor(key)
|
||||
if !protected {
|
||||
next.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
if ac.Auth == nil {
|
||||
http.Error(w, "this page is protected, but the server has no OIDC configuration",
|
||||
http.StatusServiceUnavailable)
|
||||
return
|
||||
}
|
||||
sess, ok := ac.session(r)
|
||||
if !ok {
|
||||
http.Redirect(w, r, "/login?target="+url.QueryEscape(r.URL.RequestURI()), http.StatusFound)
|
||||
return
|
||||
}
|
||||
if !acl.Allows(sess.Groups, required) {
|
||||
http.Error(w, "403 Forbidden: your account does not have access to this page.",
|
||||
http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
// SecurityHeaders sets conservative defaults on every response.
|
||||
func (ac *AccessControl) SecurityHeaders(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
h := w.Header()
|
||||
h.Set("X-Content-Type-Options", "nosniff")
|
||||
h.Set("X-Frame-Options", "DENY")
|
||||
h.Set("Referrer-Policy", "strict-origin-when-cross-origin")
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Klagarge/yggdrasil/internal/oidc"
|
||||
)
|
||||
|
||||
// loginState remembers where a pending OIDC flow must return.
|
||||
type loginState struct {
|
||||
target string
|
||||
expires time.Time
|
||||
}
|
||||
|
||||
// session reads the session cookie and returns the session if valid.
|
||||
func (ac *AccessControl) session(r *http.Request) (*Session, bool) {
|
||||
c, err := r.Cookie(sessionCookie)
|
||||
if err != nil || c.Value == "" {
|
||||
return nil, false
|
||||
}
|
||||
return ac.Sessions.Get(c.Value)
|
||||
}
|
||||
|
||||
func (ac *AccessControl) setSessionCookie(w http.ResponseWriter, id string) {
|
||||
http.SetCookie(w, &http.Cookie{
|
||||
Name: sessionCookie,
|
||||
Value: id,
|
||||
Path: "/",
|
||||
HttpOnly: true,
|
||||
Secure: ac.CookieSecure,
|
||||
SameSite: http.SameSiteLaxMode,
|
||||
MaxAge: int(ac.Sessions.TTL() / time.Second),
|
||||
})
|
||||
}
|
||||
|
||||
func (ac *AccessControl) clearSessionCookie(w http.ResponseWriter) {
|
||||
http.SetCookie(w, &http.Cookie{
|
||||
Name: sessionCookie,
|
||||
Value: "",
|
||||
Path: "/",
|
||||
HttpOnly: true,
|
||||
Secure: ac.CookieSecure,
|
||||
SameSite: http.SameSiteLaxMode,
|
||||
MaxAge: -1,
|
||||
})
|
||||
}
|
||||
|
||||
// Login redirects to the OIDC provider, remembering where to return.
|
||||
func (ac *AccessControl) Login(w http.ResponseWriter, r *http.Request) {
|
||||
if ac.Auth == nil {
|
||||
http.Error(w, "OIDC is not configured on this server", http.StatusServiceUnavailable)
|
||||
return
|
||||
}
|
||||
target := sanitizeTarget(r.URL.Query().Get("target"))
|
||||
state := randomID()
|
||||
ac.saveState(state, target)
|
||||
http.Redirect(w, r, ac.Auth.AuthCodeURL(state), http.StatusFound)
|
||||
}
|
||||
|
||||
// Callback finishes the OIDC flow: it verifies the provider's response and
|
||||
// starts a session.
|
||||
func (ac *AccessControl) Callback(w http.ResponseWriter, r *http.Request) {
|
||||
if ac.Auth == nil {
|
||||
http.Error(w, "OIDC is not configured on this server", http.StatusServiceUnavailable)
|
||||
return
|
||||
}
|
||||
target, ok := ac.takeState(r.URL.Query().Get("state"))
|
||||
if !ok {
|
||||
http.Error(w, "unknown or expired login state; restart the login", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
code := r.URL.Query().Get("code")
|
||||
if code == "" {
|
||||
http.Error(w, "missing authorization code", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
token, err := ac.Auth.Exchange(r.Context(), code)
|
||||
if err != nil {
|
||||
http.Error(w, "token exchange failed", http.StatusBadGateway)
|
||||
return
|
||||
}
|
||||
idToken, err := ac.Auth.VerifyIDToken(r.Context(), token)
|
||||
if err != nil {
|
||||
http.Error(w, "invalid identity token", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
profile, err := oidc.ProfileFromToken(idToken)
|
||||
if err != nil {
|
||||
http.Error(w, "unreadable identity token claims", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
ac.setSessionCookie(w, ac.Sessions.Create(profile))
|
||||
http.Redirect(w, r, target, http.StatusFound)
|
||||
}
|
||||
|
||||
// Logout ends the session.
|
||||
func (ac *AccessControl) Logout(w http.ResponseWriter, r *http.Request) {
|
||||
if c, err := r.Cookie(sessionCookie); err == nil && c.Value != "" {
|
||||
ac.Sessions.Delete(c.Value)
|
||||
}
|
||||
ac.clearSessionCookie(w)
|
||||
http.Redirect(w, r, "/", http.StatusFound)
|
||||
}
|
||||
|
||||
func (ac *AccessControl) saveState(state, target string) {
|
||||
ac.stateMu.Lock()
|
||||
now := time.Now()
|
||||
for k, v := range ac.states { // opportunistic purge
|
||||
if now.After(v.expires) {
|
||||
delete(ac.states, k)
|
||||
}
|
||||
}
|
||||
ac.states[state] = loginState{target: target, expires: now.Add(stateTTL)}
|
||||
ac.stateMu.Unlock()
|
||||
}
|
||||
|
||||
func (ac *AccessControl) takeState(state string) (string, bool) {
|
||||
if state == "" {
|
||||
return "", false
|
||||
}
|
||||
ac.stateMu.Lock()
|
||||
defer ac.stateMu.Unlock()
|
||||
s, ok := ac.states[state]
|
||||
if ok {
|
||||
delete(ac.states, state) // states are single use
|
||||
}
|
||||
if !ok || time.Now().After(s.expires) {
|
||||
return "", false
|
||||
}
|
||||
return s.target, true
|
||||
}
|
||||
|
||||
// sanitizeTarget only accepts local redirect targets (open-redirect guard).
|
||||
func sanitizeTarget(t string) string {
|
||||
if t == "" || !strings.HasPrefix(t, "/") || strings.HasPrefix(t, "//") || strings.HasPrefix(t, "/\\") {
|
||||
return "/"
|
||||
}
|
||||
return t
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/Klagarge/yggdrasil/internal/oidc"
|
||||
)
|
||||
|
||||
func TestSanitizeTarget(t *testing.T) {
|
||||
cases := map[string]string{
|
||||
"": "/",
|
||||
"/thermo/fluids.html": "/thermo/fluids.html",
|
||||
"/a b/c.html?x=1": "/a b/c.html?x=1",
|
||||
"//evil.example.com": "/",
|
||||
"/\\evil.example.com": "/",
|
||||
"https://evil.example.com": "/",
|
||||
"relative.html": "/",
|
||||
}
|
||||
for in, want := range cases {
|
||||
if got := sanitizeTarget(in); got != want {
|
||||
t.Errorf("sanitizeTarget(%q) = %q, want %q", in, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestStateRoundTrip(t *testing.T) {
|
||||
ac := NewAccessControl(nil, NewSessionStore(0), nil, false)
|
||||
|
||||
ac.saveState("s1", "/thermo/fluids.html")
|
||||
target, ok := ac.takeState("s1")
|
||||
if !ok || target != "/thermo/fluids.html" {
|
||||
t.Fatalf("state round trip failed: %q %v", target, ok)
|
||||
}
|
||||
// States are single use.
|
||||
if _, ok := ac.takeState("s1"); ok {
|
||||
t.Error("state must be single use")
|
||||
}
|
||||
// Unknown states are rejected (CSRF guard).
|
||||
if _, ok := ac.takeState("nope"); ok {
|
||||
t.Error("unknown state must be rejected")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSessionExpiry(t *testing.T) {
|
||||
store := NewSessionStore(0) // zero TTL: expires immediately
|
||||
id := store.Create(oidc.Profile{Subject: "sub", Email: "user@example.com", Groups: []string{"family"}})
|
||||
if _, ok := store.Get(id); ok {
|
||||
t.Error("expired session must not validate")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/Klagarge/yggdrasil/internal/oidc"
|
||||
)
|
||||
|
||||
// Session is an authenticated user.
|
||||
type Session struct {
|
||||
Subject string
|
||||
Email string
|
||||
Name string
|
||||
Groups []string
|
||||
Expires time.Time
|
||||
}
|
||||
|
||||
// SessionStore keeps sessions in memory. Sessions do not survive server
|
||||
// restarts; users simply log in again.
|
||||
type SessionStore struct {
|
||||
mu sync.RWMutex
|
||||
sessions map[string]*Session
|
||||
ttl time.Duration
|
||||
}
|
||||
|
||||
// NewSessionStore creates a store with the given session lifetime.
|
||||
func NewSessionStore(ttl time.Duration) *SessionStore {
|
||||
return &SessionStore{
|
||||
sessions: make(map[string]*Session),
|
||||
ttl: ttl,
|
||||
}
|
||||
}
|
||||
|
||||
// TTL reports the session lifetime.
|
||||
func (s *SessionStore) TTL() time.Duration { return s.ttl }
|
||||
|
||||
// Create registers a new session and returns its ID.
|
||||
func (s *SessionStore) Create(p oidc.Profile) string {
|
||||
id := randomID()
|
||||
s.mu.Lock()
|
||||
s.sessions[id] = &Session{
|
||||
Subject: p.Subject,
|
||||
Email: p.Email,
|
||||
Name: p.Name,
|
||||
Groups: p.Groups,
|
||||
Expires: time.Now().Add(s.ttl),
|
||||
}
|
||||
s.mu.Unlock()
|
||||
return id
|
||||
}
|
||||
|
||||
// Get returns the session for id if it exists and has not expired.
|
||||
func (s *SessionStore) Get(id string) (*Session, bool) {
|
||||
s.mu.RLock()
|
||||
sess, ok := s.sessions[id]
|
||||
s.mu.RUnlock()
|
||||
if !ok || time.Now().After(sess.Expires) {
|
||||
return nil, false
|
||||
}
|
||||
return sess, true
|
||||
}
|
||||
|
||||
// Delete removes a session.
|
||||
func (s *SessionStore) Delete(id string) {
|
||||
s.mu.Lock()
|
||||
delete(s.sessions, id)
|
||||
s.mu.Unlock()
|
||||
}
|
||||
|
||||
// StartJanitor purges expired sessions periodically until ctx is done.
|
||||
// Call it in a goroutine.
|
||||
func (s *SessionStore) StartJanitor(ctx context.Context, every time.Duration) {
|
||||
t := time.NewTicker(every)
|
||||
defer t.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case now := <-t.C:
|
||||
s.mu.Lock()
|
||||
for id, sess := range s.sessions {
|
||||
if now.After(sess.Expires) {
|
||||
delete(s.sessions, id)
|
||||
}
|
||||
}
|
||||
s.mu.Unlock()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func randomID() string {
|
||||
var b [32]byte
|
||||
if _, err := rand.Read(b[:]); err != nil {
|
||||
panic(err) // crypto/rand does not fail on supported platforms
|
||||
}
|
||||
return base64.RawURLEncoding.EncodeToString(b[:])
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
// Package oidc implements the OIDC client (authorization code flow) used to
|
||||
// authenticate users. It is the only place that talks to the identity
|
||||
// provider (e.g. Authentik).
|
||||
package oidc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/coreos/go-oidc/v3/oidc"
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
// Config is the OIDC client configuration, read from the environment by
|
||||
// LoadConfigFromEnv.
|
||||
type Config struct {
|
||||
ClientID string
|
||||
ClientSecret string
|
||||
IssuerURL string
|
||||
RedirectURL string
|
||||
Scopes string
|
||||
}
|
||||
|
||||
// LoadConfigFromEnv reads the OIDC_* variables. It returns an error when
|
||||
// OIDC is not (fully) configured; the caller decides whether that is fatal.
|
||||
func LoadConfigFromEnv() (*Config, error) {
|
||||
cfg := &Config{
|
||||
ClientID: os.Getenv("OIDC_CLIENT_ID"),
|
||||
ClientSecret: os.Getenv("OIDC_CLIENT_SECRET"),
|
||||
IssuerURL: os.Getenv("OIDC_ISSUER_URL"),
|
||||
RedirectURL: os.Getenv("OIDC_REDIRECT_URL"),
|
||||
Scopes: os.Getenv("OIDC_SCOPES"),
|
||||
}
|
||||
if cfg.Scopes == "" {
|
||||
cfg.Scopes = "openid profile email groups"
|
||||
}
|
||||
if cfg.ClientID == "" || cfg.IssuerURL == "" || cfg.RedirectURL == "" {
|
||||
return nil, errors.New("OIDC_CLIENT_ID, OIDC_ISSUER_URL and OIDC_REDIRECT_URL must be set")
|
||||
}
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
// Authenticator drives the authorization code flow and verifies ID tokens.
|
||||
type Authenticator struct {
|
||||
oauth2 oauth2.Config
|
||||
verifier *oidc.IDTokenVerifier
|
||||
}
|
||||
|
||||
// NewAuthenticator discovers the provider at the issuer URL. It fails fast
|
||||
// when the issuer is unreachable so that a misconfigured server never
|
||||
// starts serving protected content.
|
||||
func NewAuthenticator(ctx context.Context, cfg *Config) (*Authenticator, error) {
|
||||
provider, err := oidc.NewProvider(ctx, cfg.IssuerURL)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("querying OIDC provider %s: %w", cfg.IssuerURL, err)
|
||||
}
|
||||
return &Authenticator{
|
||||
oauth2: oauth2.Config{
|
||||
ClientID: cfg.ClientID,
|
||||
ClientSecret: cfg.ClientSecret,
|
||||
RedirectURL: cfg.RedirectURL,
|
||||
Endpoint: provider.Endpoint(),
|
||||
Scopes: strings.Fields(cfg.Scopes),
|
||||
},
|
||||
verifier: provider.Verifier(&oidc.Config{ClientID: cfg.ClientID}),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// AuthCodeURL returns the URL the user is redirected to for login.
|
||||
func (a *Authenticator) AuthCodeURL(state string) string {
|
||||
return a.oauth2.AuthCodeURL(state)
|
||||
}
|
||||
|
||||
// Exchange trades an authorization code for tokens.
|
||||
func (a *Authenticator) Exchange(ctx context.Context, code string) (*oauth2.Token, error) {
|
||||
return a.oauth2.Exchange(ctx, code)
|
||||
}
|
||||
|
||||
// VerifyIDToken extracts and verifies the ID token issued in the code
|
||||
// exchange. Signature, issuer, audience and expiry are checked.
|
||||
func (a *Authenticator) VerifyIDToken(ctx context.Context, token *oauth2.Token) (*oidc.IDToken, error) {
|
||||
rawIDToken, ok := token.Extra("id_token").(string)
|
||||
if !ok {
|
||||
return nil, errors.New("no id_token in OAuth2 token response")
|
||||
}
|
||||
return a.verifier.Verify(ctx, rawIDToken)
|
||||
}
|
||||
|
||||
// Profile holds the user attributes used for sessions and logging.
|
||||
type Profile struct {
|
||||
Subject string
|
||||
Email string
|
||||
Name string
|
||||
Groups []string
|
||||
}
|
||||
|
||||
// ProfileFromToken reads user attributes from a verified ID token. A missing
|
||||
// groups claim yields nil groups, which denies every protected page
|
||||
// (fail closed).
|
||||
func ProfileFromToken(idToken *oidc.IDToken) (Profile, error) {
|
||||
var claims struct {
|
||||
Name string `json:"name"`
|
||||
Email string `json:"email"`
|
||||
Groups []string `json:"groups"`
|
||||
}
|
||||
if err := idToken.Claims(&claims); err != nil {
|
||||
return Profile{}, fmt.Errorf("reading ID token claims: %w", err)
|
||||
}
|
||||
return Profile{
|
||||
Subject: idToken.Subject,
|
||||
Email: claims.Email,
|
||||
Name: claims.Name,
|
||||
Groups: claims.Groups,
|
||||
}, nil
|
||||
}
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -0,0 +1,4 @@
|
||||
# Master of science in engineering in embedded system
|
||||
|
||||
## [SRE - Software Reverse Engineering](/MSE/SRE/)
|
||||
- [Tools](/MSE/SRE/tools.md)
|
||||
@@ -0,0 +1,5 @@
|
||||
<header><h1>
|
||||
SRE - Software Reverse Engineering
|
||||
</h1></header>
|
||||
|
||||
- [Tools](/MSE/SRE/tools.md)
|
||||
@@ -0,0 +1,129 @@
|
||||
<header>
|
||||
<h1>Tools</h1>
|
||||
</header>
|
||||
|
||||
|
||||
# Utilities (Linux)
|
||||
|
||||
## strings
|
||||
For each file given, `strings` prints the printable character sequences that are at least 4 characters long (or the
|
||||
number given with the options below) and are followed by an unprintable character. By default, it only prints the
|
||||
strings from the initialized and loaded sections of object files. For other types of files, it prints the strings
|
||||
from the whole file. The main use of `strings` is to find the contents of non-text files.
|
||||
|
||||
```terminal
|
||||
$|strings code/a.out
|
||||
The sum of 1 to 63 is %d.
|
||||
```
|
||||
|
||||
## file
|
||||
Determine file type
|
||||
|
||||
```terminal
|
||||
$|file /bin/ls
|
||||
/bin/ls: ELF 64-bit LSB executable, x86-64, version 1 (SYSV),
|
||||
dynamically linked (uses shared libs), for GNU/Linux 2.6.18, stripped
|
||||
$|file /bin/ls
|
||||
/bin/ls: Mach-O 64-bit executable x86_64
|
||||
```
|
||||
|
||||
## nm
|
||||
List the symbols in a object file
|
||||
|
||||
## readelf
|
||||
Displays information about ELF files
|
||||
|
||||
## ldd
|
||||
Displays the shared objects dependencies
|
||||
|
||||
## lsof
|
||||
Lists files and other system ressources opened by a process
|
||||
|
||||
## ltrace
|
||||
Traces the calls of a process to functions in dynamic libraries
|
||||
|
||||
## strace
|
||||
Traces the systems calls of a process
|
||||
|
||||
# Disassemblers
|
||||
|
||||
## objdump, otool on macOS
|
||||
Open-source tools for showing different types of information about object files on Unix-based operating systems.
|
||||
|
||||
## disassemble
|
||||
Internal command of gdb
|
||||
|
||||
## [radare2](https://rada.re/n/radare2.html)
|
||||
Open source portable reversing framework
|
||||
|
||||
## [Capstone](https://www.capstone-engine.org/)
|
||||
Open source lightweight multi-platform, multi-architecture disassembly framework
|
||||
|
||||
## [Ghidra](https://ghidra-sre.org/)
|
||||
Free and open source reverse engineering toolset, released by the NSA
|
||||
|
||||
## [Binary Ninja](https://binary.ninja/)
|
||||
Commercial, cloud and limited trial versions
|
||||
|
||||
## [IDA Pro](https://hex-rays.com/ida-pro/)
|
||||
Free and commercial versions
|
||||
|
||||
## [Hopper](https://www.hopperapp.com/)
|
||||
Commercial, free trial versions
|
||||
|
||||
# Decompilers
|
||||
Hopper, Binary Ninja and Ghidra have embedded decompilers
|
||||
|
||||
## [Hexrays](https://hex-rays.com/decompiler/)
|
||||
Commercial plugin for IDA Pro
|
||||
|
||||
## [.NET Reflector](https://www.red-gate.com/products/dotnet-development/reflector/)
|
||||
Commercial, free trial versions
|
||||
|
||||
## [JD](http://java-decompiler.github.io/)
|
||||
Free and open source Java decompiler
|
||||
|
||||
## [uncompyle6](https://pypi.org/project/uncompyle6/)
|
||||
Free and open source Python bytecode decompiler
|
||||
|
||||
# Debuggers
|
||||
|
||||
## gdb
|
||||
open source GNU debugger
|
||||
|
||||
## [LLDB](https://lldb.llvm.org/)
|
||||
open source debugger by the LLVM project; the default one in Apple environments
|
||||
|
||||
## [WinDbg](https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/debugger-download-tools)
|
||||
multipurpose debugger for the Microsoft Windows computer operating system, distributed by Microsoft
|
||||
|
||||
## [x64dbg](https://x64dbg.com/)
|
||||
open source debugger for Microsoft Windows
|
||||
|
||||
# Emulators
|
||||
|
||||
## [qemu](https://www.qemu.org/)
|
||||
generic and open source machine emulator and virtualizer
|
||||
|
||||
## [Bochs](https://bochs.sourceforge.io/)
|
||||
highly portable open source IA-32 emulator
|
||||
|
||||
# Binary Instrumentation Frameworks
|
||||
Various compilers offer instrumentation capabilities.
|
||||
|
||||
## [PIN](https://www.intel.com/content/www/us/en/developer/articles/tool/pin-a-dynamic-binary-instrumentation-tool.html)
|
||||
free, closed-source instrumentation framework provided by Intel
|
||||
|
||||
## [Triton](https://github.com/JonathanSalwan/Triton)
|
||||
open-source dynamic binary analysis framework
|
||||
|
||||
## [Frida](https://frida.re/)
|
||||
open-source dynamic instrumentation toolkit for developers, reverse-engineers, and security researchers
|
||||
|
||||
## [QBDI](https://qbdi.quarkslab.com/)
|
||||
modular, cross-platform and cross-architecture DBI framework
|
||||
|
||||
|
||||
```terminal
|
||||
|
||||
```
|
||||
@@ -0,0 +1,2 @@
|
||||
* [SRE - Software Reverse Engineering](/MSE/SRE/)
|
||||
* [Tools](/MSE/SRE/tools.md)
|
||||
@@ -0,0 +1,33 @@
|
||||
<header>
|
||||
<h1>Yggdrasil</h1>
|
||||
</header>
|
||||
Yggdrasil is a collections of notes around IT, OT and other geeks stuff
|
||||
<br>
|
||||
<br>
|
||||
<br>
|
||||
|
||||
# [Bachelor in embedded system](/infotro/)
|
||||
Here notes I take during my bachelor in embedded systems
|
||||
|
||||
Mainly some topics:
|
||||
- [PTR - Programmation Temps Réel](/infotro/PTR/)
|
||||
- [SDi - Système Distribué](/infotro/SDi/)
|
||||
- [Eln - Électronique](/infotro/Eln/)
|
||||
- [SEm - Systèmes Embarqué](/infotro/SEm/)
|
||||
|
||||
# [Master of science in engineering in embedded system](/MSE/)
|
||||
Here note I take during my Master of science un engineering with embedded system as orientation
|
||||
|
||||
Mainly some topics:
|
||||
- [SRE - Software Reverse Engineering](/MSE/SRE/)
|
||||
|
||||
# [Embedded Systems](/Embed/)
|
||||
Here some note specific about some Embedded System
|
||||
- [STM32](/Embed/STM32/)
|
||||
- [nRF](/Embed/nRF/)
|
||||
|
||||
# [Working environment at HES-SO](/HES-SO/)
|
||||
Here some note about the working environment at HES-SO
|
||||
|
||||
- [OneDrive - Linux](/HES-SO/OneDrive.md)
|
||||
- [Teams - Linux](/HES-SO/Teams.md)
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -3,13 +3,11 @@
|
||||
* [SDi - Systèmes Distribués](/infotro/SDi/)
|
||||
* [Eln - Électronique](/infotro/Eln/)
|
||||
* [SEm - Systèmes Embarqués](/infotro/SEm/)
|
||||
* [MSE](/MSE/)
|
||||
* [SRE](/MSE/SRE/)
|
||||
* [Embed](/Embed/)
|
||||
* [STM32](/Embed/STM32/)
|
||||
* [nRF](/Embed/nRF/)
|
||||
* [HES-SO](/HES-SO/)
|
||||
* [OneDrive - Linux](/HES-SO/OneDrive.md)
|
||||
* [Teams - Linux](/HES-SO/Teams.md)
|
||||
* [Wireless Communication](/Wireless-Communication/)
|
||||
* [Protocol](/Wireless-Communication/Protocol/)
|
||||
* [Train](/Wireless-Communication/Train/)
|
||||
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -189,6 +189,9 @@
|
||||
<link rel="stylesheet" href="//unpkg.com/docsify-accordion/src/style.css">
|
||||
<script src="//unpkg.com/docsify-accordion/src/index.js"></script>
|
||||
|
||||
<!----- Terminal ----->
|
||||
<script src="https://cdn.jsdelivr.net/npm/docsify-terminal-block@1.0.3"></script>
|
||||
|
||||
<!----- Last time updated ----->
|
||||
<script src="https://cdn.jsdelivr.net/npm/docsify-updated/src/time-updater.js"></script>
|
||||
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Whitespace-only changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Loaded 100 of 235 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user