-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
docs: fill recipe pages with content
- Loading branch information
the-dipsy
committed
Mar 15, 2024
1 parent
c96853d
commit 2a2719e
Showing
5 changed files
with
372 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,60 @@ | ||
--- | ||
parent: Recipes | ||
nav_order: 50 | ||
nav_order: 30 | ||
--- | ||
|
||
# Component Libraries | ||
Outsourcing parts of your configuration into reusable libraries is a great way | ||
to keep your projects manageable and create a good foundation for future ones. | ||
You can even nest libraries as deeply as you like. | ||
|
||
You won't have to learn a new build system or package manager, because | ||
you can simply employ *Git* submodules for this. | ||
|
||
## Creating a Library | ||
All you need to do to create a library is initialize a separate repository | ||
(`git init`) and populate it with your source files. | ||
|
||
The [Project Structure][structure] page's recommendations about version control | ||
and main components apply to libraries as well, except for the convention to | ||
refrain from taking positional and keyword arguments in your main component. | ||
The parameters defined in your main (and other) component's signatures should | ||
in fact be the only way to parameterize your library. | ||
|
||
Projects incorporating your library can, of course, access all of its files and | ||
components. Depending on its purpose, it might make sense to design your | ||
library to expose its entire functionality through the main component, though. | ||
|
||
What's only a strong recommendation in the context of standalone projects is | ||
vital when it comes to libraries: All references to files and components within | ||
your library must be relative, while references to standard library components | ||
must be absolute. Use `_.my_component()` but | ||
`magic(load.toml(_/"config.toml"))`. Otherwise, your library will break when | ||
moved within the directory structure. | ||
|
||
The return values of your library can be whatever you need. They might be | ||
completely custom data, functions, classes, etc. They might also be a list or | ||
dict to populate some field of the *Butane* configuration you are constructing. | ||
Or they might be a complete configuration, ready to be merged with the rest of | ||
your projects' configs using the [*merge* standard library component][merge]. | ||
|
||
When your library has arrived at a stable state, consider adding a version tag | ||
(`git tag vX.Y.Z`) to easily manage different projects using different versions | ||
of your library. | ||
|
||
[structure]: recipes-structure.html | ||
[merge]: components-stdlib.html#create-merge-fields-for-inline-local-andor-remote-configs | ||
|
||
## Adding a Library to Your Project | ||
A good place to add all your project's libraries is the *lib* directory. Its | ||
purpose will be obvious, you'll know where to look for your libraries, and you | ||
avoid cluttering your repository's root directory with external code. | ||
|
||
To add a library to your project, simply add it as a *Git* submodule and | ||
optionally check out a version tag. Execute, e.g., `git submodule add SOURCE | ||
lib/NAME && cd lib/NAME && git checkout vX.Y.Z` to add a library from a | ||
specific source at a specific version to your *lib* directory with a specific | ||
name. Don't forget to add and commit these changes in your project repository. | ||
|
||
If your library has a *main.pyro* component, you can now simply reference and | ||
execute it using `_.lib.NAME(...)`. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,113 @@ | ||
--- | ||
parent: Recipes | ||
nav_order: 60 | ||
nav_order: 40 | ||
--- | ||
|
||
# Remote Configuration | ||
Serving your configurations over *HTTP(S)* has the great advantage that you | ||
neither have to include any potentially secret configuration details for your | ||
final system on your unencrypted installer media, nor do you need to create new | ||
media when your configuration changes. | ||
|
||
This page outlines the installation of an encrypted server with mutually | ||
secured remote configuration loading. | ||
|
||
The machine you run *Pyromaniac* on and the one you want the system to be | ||
installed on need to share a network, and your firewall and *NAT* | ||
configurations need to allow the target machine to open *TCP* connections | ||
to a port on the machine running *Pyromaniac*. The shared network can be the | ||
internet, but a more restricted one would be preferable to reduce the attack | ||
surface. If your IT infrastructure allows it, you may even directly connect the | ||
two machines using some network cable and static IP addressing. | ||
|
||
## Installer Preparation | ||
The first thing we need to do is create our *ISO* installer. You'll need to | ||
know the disk device you want to install *CoreOS* to and your *Pyromaniac* | ||
machine's address. | ||
|
||
Assuming you would like to install to */dev/sda* and your *Pyromaniac* *HTTPS* | ||
server will run on port *4433* at *192.168.0.10*, you can create your installer | ||
in a single command: | ||
|
||
```sh | ||
pyromaniac --iso --iso-disk /dev/sda \ | ||
--address 'https://192.168.0.10:4433/' \ | ||
<<< '`remote.merge()`' > installer.iso | ||
``` | ||
|
||
A self-signed certificate and random credentials for encryption and mutual | ||
authentication will be embedded into the installer by default. | ||
|
||
## Setting Up Disk Encryption | ||
To use disk encryption in *CoreOS*, you'll need to use *Clevis* pinning as | ||
[described in the CoreOS docs][luks]. You may still want to choose and store | ||
the encryption keys for your root partition and further data partitions | ||
yourself. | ||
|
||
This configuration piece will encrypt your root partition with a key of your | ||
choosing to demonstrate the retrieval of secret keys over *HTTP(S)*. This would | ||
be more useful for persistent data partitions in practice. Consider using | ||
*Clevis* pinning for the root partition instead as [described in the CoreOS | ||
docs][luks]. The configuration described here will break unattended upgrades | ||
because you'll need to manually type in the encryption key every time the | ||
machine is rebooted. | ||
|
||
```yaml | ||
storage.luks[0]: | ||
name: root | ||
label: luks-root | ||
device: /dev/disk/by-partlabel/root | ||
key_file: `contents(remote.url / "root.secret", remote.headers)` | ||
wipe_volume: true | ||
``` | ||
Remember that the `remote` variable will only be available in your main | ||
component. If you configure storage in a separate component, you'll need to | ||
pass the *URL* and headers as arguments to it. | ||
|
||
[luks]: https://docs.fedoraproject.org/en-US/fedora-coreos/storage/#_encrypted_storage_luks | ||
|
||
## Perform the Installation | ||
You can now start the *Pyromaniac* *HTTPS* server with the same address used | ||
for the *ISO* generation using the following command: | ||
|
||
``` | ||
pyromaniac --serve --address 'https://192.168.0.10:4433/' . | ||
``` | ||
|
||
If you boot your installer medium, it will request the */config.ign* path from | ||
your server, which will compile your configuration and send it back to the | ||
installer. During the storage setup, the installer will request the encryption | ||
key from the */root.secret* path. *Pyromaniac* will prompt you for the secret | ||
in the terminal and respond to the request with whatever line you type. | ||
|
||
The installer might request your configuration multiple times during the | ||
installation process, and it will be recompiled every time. You might run into | ||
problems if your code is not deterministic and depends on randomness to compile | ||
your configuration. | ||
|
||
After the installation finishes, the server will be up and running as specified | ||
in your configuration. | ||
|
||
## Serving Static Configurations | ||
You might already have a readily compiled *Ignition* file or don't want the | ||
server to recompile your configuration every time if compilation takes a bit | ||
longer. | ||
|
||
You can easily serve an *Ignition* file named *config.ign* by writing a | ||
one-liner using the `ignition.config.replace` field: | ||
|
||
``` | ||
pyromaniac --serve --address 'https://192.168.0.10:4433/' \ | ||
<<< 'ignition.config.replace: `contents(Path("config.ign"))`' | ||
``` | ||
|
||
## For Debugging | ||
Remote configuration loading can accelerate testing of your configs in virtual | ||
machines as well. Start the installation as usual but make a snapshot right | ||
before the configuration is loaded. You can then restore that snapshot whenever | ||
you'd like to test a new version of your configuration and have the | ||
provisioning of your machine happen within seconds. | ||
|
||
Since the *HTTP(S)* server will recompile your configuration on every request, | ||
you can simply keep it running while tinkering with your configs. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,91 @@ | ||
--- | ||
parent: Recipes | ||
nav_order: 40 | ||
nav_order: 60 | ||
--- | ||
|
||
# Rootless Podman | ||
Even though you can easily embed raw executables and systemd services into your | ||
deployments, *Fedora CoreOS* comes with *docker* and *podman* preinstalled and | ||
is optimized for container workloads. | ||
|
||
In regards to building secure systems, the *podman* engine has the major | ||
advantage of being geared towards rootless containers. You can use this to add | ||
an extra layer of separation by running different services as different *Linux* | ||
users. When an attacker manages to compromise your service and also to break | ||
out of the container, they will still not have control over the entire system | ||
or other containers running on it. | ||
|
||
## Systemd Units | ||
The recommended way to manage *podman* containers outside of platforms like | ||
*Kubernetes* is using [Quadlet][quadlet]. *Quadlet* allows you to specify your | ||
container deployments inside special kinds of *systemd* unit files like the | ||
following. | ||
|
||
`my-service.container` | ||
```ini | ||
[Unit] | ||
Description=My Service | ||
|
||
[Container] | ||
Image=docker.io/my/image | ||
|
||
[Service] | ||
Restart=always | ||
|
||
[Install] | ||
WantedBy=multi-user.target | ||
``` | ||
|
||
You can use the *tree* standard library component to include an entire | ||
directory of such units into your deployment like this: | ||
|
||
```python | ||
--- | ||
dirs = directories(".", ".config/containers", "myuser") | ||
units = tree(".config/containers/systemd", _/"units", "myuser") | ||
--- | ||
|
||
storage: | ||
directories: `dirs + units['directories']` | ||
files: `units['files']` | ||
``` | ||
|
||
[quadlet]: https://docs.podman.io/en/latest/markdown/podman-systemd.unit.5.html | ||
|
||
## Templating | ||
The great thing about *Pyromaniac* is that you can outsource code and build | ||
abstractions very easily. You can write a component for constructing a unit | ||
file from a Jinja template or even for creating units from scratch. | ||
|
||
A component for creating *Quadlet* units from scratch might look something like | ||
this: | ||
|
||
`quadlet.pyro` | ||
```python | ||
(user: str, name: str, ext: str = "container", **sections: dict[str, str] = []) | ||
--- | ||
lines = [] | ||
for section, fields in sections.items(): | ||
lines.append(f"[{section.capitalize()}]") | ||
for key, value in fields.items(): | ||
key = "".join(w.capitalize() for w in key.split("_")) | ||
lines.append(f"{key}={value}") | ||
|
||
file(f".config/containers/systemd/{name}.{ext}", "\n".join(lines), user) | ||
``` | ||
|
||
It could be used to add a file for the unit from above as follows: | ||
|
||
```python | ||
--- | ||
unit = quadlet( | ||
"myuser", "my-service", | ||
unit={"description": "My Service"}, | ||
container={"image": "docker.io/my/image"}, | ||
service={"restart": "always"}, | ||
install={"wanted_by": "multi-user.target"}, | ||
) | ||
--- | ||
|
||
storage.files[0]: `unit` | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.