Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add pillar and use it to configure the state #9

Merged
merged 4 commits into from
Feb 27, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 15 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
User Salt States for Qubes OS
=============================

A collection of user Salt states for Qubes OS.
A collection of user Salt formulas for Qubes OS.

- **Split-SSH** ([sources][split-ssh-src], [packaging][split-ssh-pkg])

[split-ssh-src]: ./states/split-ssh/src
[split-ssh-pkg]: ./states/split-ssh
[split-ssh-src]: ./packages/split-ssh/src
[split-ssh-pkg]: ./packages/split-ssh

Usage
-----

### Prerequisites

Enable the Salt [user directories][qubes-user-dirs] if you haven't already. This allows to install Salt states without mixing them with the `base` states that are maintained by the Qubes OS team.
Enable the Salt [user directories][qubes-user-dirs] if you haven't already. This allows to install Salt formulas without mixing them with the `base` formulas that are maintained by the Qubes OS team.

```sh
sudo qubesctl top.enable qubes.user-dirs
Expand All @@ -29,11 +29,11 @@ sudo qubesctl state.apply

### Step-by-step

This repository contains multiple states and it is not required to use them all. For each of them, the same enabling steps apply, and `split-ssh` will be used as an example.
This repository contains multiple formulas and it is not required to use them all. For each of them, the same enabling steps apply, and `split-ssh` will be used as an example.

#### Install the state
#### Install the formula

In order to be able to use a state, its definition should be present in the `/srv/user_salt/` directory of _dom0_.
In order to be able to use a formula, the state definition should be present in the `/srv/user_salt/` directory of _dom0_, and the pillar definition in `/srv/user_pillar`.


> ⚠ **Security warning**: Since any domain is _less trusted_ than _dom0_ (by definition), copying anything into _dom0_ requires extreme caution. See [References](#references) for details, and use own judgement.
Expand All @@ -43,11 +43,15 @@ In order to be able to use a state, its definition should be present in the `/sr
[secure-update]: https://www.qubes-os.org/doc/dom0-secure-updates


No matter how you decide to perform this step, the end result should be a directory containing one or more `.top` files (and the corresponding `.sls` and other files):
No matter how you decide to perform this step, the end result should be a directory containing one or more `.top` files (and the corresponding `.sls` and other files), as well as a directory containing a `config.yaml` file:

```sh
/srv/user_salt/split-ssh
/srv/user_pillar/split-ssh # contains the configuration
/srv/user_salt/split-ssh # contains the .top files
```
#### Adjust the configuration to fit your needs

The state will be defined by the configuration stored in the pillar. In our example, you can find the default configuration in `/srv/user_pillar/split-ssh/config.yaml` and modify it to fit your needs.

#### Enable the state

Expand Down Expand Up @@ -91,7 +95,7 @@ sudo qubesctl --all state.apply
```

Each state targets one or more qubes, and if you know which qubes you're modifying you can save some time by targetting them specifically.
For example, the `split-ssh` state targets `dom0` and the `fedora-32` template, as well as two qubes called `ssh-vault` and `ssh-client`:
For example, the `split-ssh` state targets `dom0` by default, as well as the `fedora-32` template, and two qubes called `ssh-vault` and `ssh-client`:

```sh
sudo qubesctl --targets=fedora-32,ssh-client,ssh-vault state.apply
Expand All @@ -100,7 +104,7 @@ sudo qubesctl --targets=fedora-32,ssh-client,ssh-vault state.apply
Note that _dom0_ is always implicitly targetted by `qubesctl` (and appears in the output as `local`). If you know it doesn't need to be updated, you can skip _dom0_:

```sh
sudo qubesctl --skip-dom0 --targets=fedora-32,ssh-client,ssh-vault state.apply # in this example, that would be enough if the qubes already exist
sudo qubesctl --skip-dom0 --targets=fedora-32,ssh-client,ssh-vault state.apply # in this example, that would be enough if the client and vault qubes already exist
```

References
Expand Down
23 changes: 22 additions & 1 deletion packages/split-ssh/src/split-ssh-formula/Makefile
Original file line number Diff line number Diff line change
@@ -1,14 +1,35 @@
PREFIX ?= /srv/user_salt

# Assumes layout but allows modification of PREFIX
PILLAR_PREFIX ?= ${PREFIX}/../user_pillar

# The path of the state files
SALT_STATE_PATH = state/

# The path of the pillar files
SALT_PILLAR_PATH = pillar/

.PHONY: all
all:
# intentionally left blank

.PHONY: install
install:
install: pillar state

.PHONY: pillar
pillar:
mkdir -p ${DESTDIR}${PILLAR_PREFIX}/split-ssh
cp ${SALT_PILLAR_PATH}client-template.sls ${DESTDIR}${PILLAR_PREFIX}/split-ssh
cp ${SALT_PILLAR_PATH}client.sls ${DESTDIR}${PILLAR_PREFIX}/split-ssh
cp ${SALT_PILLAR_PATH}clients.sls ${DESTDIR}${PILLAR_PREFIX}/split-ssh
cp ${SALT_PILLAR_PATH}config.yaml ${DESTDIR}${PILLAR_PREFIX}/split-ssh
cp ${SALT_PILLAR_PATH}init.jinja ${DESTDIR}${PILLAR_PREFIX}/split-ssh
cp ${SALT_PILLAR_PATH}vault-template.sls ${DESTDIR}${PILLAR_PREFIX}/split-ssh
cp ${SALT_PILLAR_PATH}vault.sls ${DESTDIR}${PILLAR_PREFIX}/split-ssh
cp ${SALT_PILLAR_PATH}vaults.sls ${DESTDIR}${PILLAR_PREFIX}/split-ssh

.PHONY: state
state:
mkdir -p ${DESTDIR}${PREFIX}/split-ssh
cp -r ${SALT_STATE_PATH}client ${DESTDIR}${PREFIX}/split-ssh
cp -r ${SALT_STATE_PATH}packages ${DESTDIR}${PREFIX}/split-ssh
Expand Down
25 changes: 15 additions & 10 deletions packages/split-ssh/src/split-ssh-formula/README.md
Original file line number Diff line number Diff line change
@@ -1,48 +1,53 @@
Split-SSH
=========

A Salt state that enables [split-SSH][split-ssh] in Qubes OS.
A Salt formula that enables [split-SSH][split-ssh] in Qubes OS.

Overview
--------

- Ensures the existence of a `ssh-vault` qube that starts automatically on system startup. This qube has no network access and holds SSH keys.
- Ensures the presence of a Qubes RPC policy in `dom0`. That policy ensures that the user is asked before any qube accesses the SSH keys contained in `ssh-vault`.
- Ensures a `ssh-client` qube exists. That qube is configured to allow usage of SSH but relies on the SSH keys contained in `ssh-vault`.
- Ensures the existence of a **vault** qube that starts automatically on system startup. This qube has no network access and holds SSH keys.
- Ensures the presence of a Qubes RPC policy in `dom0`. That policy ensures that the user is asked before any qube accesses the SSH keys contained in the **vault**.
- Ensures a **client** qube exists. That qube is configured to allow usage of SSH but relies on the SSH keys contained in the **vault**.

By default, the **vault** is a _qube_ called `ssh-vault` and the **client** is a _qube_ called `ssh-client`, but that configuration can be changed if needeed.

Installation
------------

### Recommended installation

Build and sign an RPM package in order to install this Salt state in _dom0_. See [qubes-mgmt-salt-user-split-ssh][rpm].
Build and sign an RPM package in order to install this Salt formula in _dom0_. See [qubes-mgmt-salt-user-split-ssh][rpm].

[rpm]: https://github.com/gonzalo-bulnes/qubes-mgmt-salt-user/tree/main/states/split-ssh
[rpm]: https://github.com/gonzalo-bulnes/qubes-mgmt-salt-user/tree/main/packages/split-ssh

### Manual installation

- Enable the Salt _user directories_ ([how-to][user-dirs-how-to])
- Copy, or type the contents of the `state/` directory into `/srv/user_salt/split-ssh/`.
- Inspect the surounding files and apply similar permissions and ownership to the `/srv/user_salt/split-ssh` directory.
- Copy, or type the contents of the `pillar/` directory into `/srv/user_pillar/split-ssh/`.
- Inspect the surounding files and apply similar permissions and ownership to the `/srv/user_salt/split-ssh` and `/srv/user_pillar/split-ssh` directories.

Usage
-----

Adapt the configuration to your needs if necessary by modifying `/srv/user_pillar/split-ssh/config.yaml`.

Enable the top files:

```sh
sudo qubesctl top.enable split-ssh.client split-ssh.policy split-ssh.vault
```

Apply the state:
Apply the state (if you modified the configuration, adjust the targets accordingly):

```sh
sudo qubesctl --targets=fedora-32,ssh-client,ssh-vault state.apply
```

**Note**: the `ssh-client` and `ssh-vault` machines will be created if they don't exist. That is the point of having a Salt state! They are part of the targets because because part of the configuration applies to them.
**Note**: the configured **client** and **vault** machines will be created if they don't exist. That is the point of using a Salt formula! They are part of the targets because because part of the configuration applies to them.

Once the state is enforced, create new SSH keys in `ssh-vault` (or copy existing keys if you prefer).
Once the state is enforced, create new SSH keys in the **vault** (or copy existing keys if you prefer).

References
----------
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
split-ssh-role:
client-template: True
Comment on lines +1 to +2
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The split-ssh-role value must be a mapping to allow a qube to assume multiple roles. (For example, fedora-32 could be both the client-template and vault-template.)

When the value is not a mapping, the latter value overrides the earlier one when the different .sls files are merged by Salt. (I believe that is standard YAML behavior.)

2 changes: 2 additions & 0 deletions packages/split-ssh/src/split-ssh-formula/pillar/client.sls
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
split-ssh-role:
client: True
7 changes: 7 additions & 0 deletions packages/split-ssh/src/split-ssh-formula/pillar/clients.sls
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
## Load config
#{% load_yaml as config %}
#{% include 'split-ssh/config.yaml' %}
#{% endload %}
---
split-ssh-clients:
{{ config.clients }}
17 changes: 17 additions & 0 deletions packages/split-ssh/src/split-ssh-formula/pillar/config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
vaults:
- name: ssh-vault
template: fedora-32
label: black
mem: 400
vcpus: 2
autostart: True

clients:
- name: ssh-client
template: fedora-32
label: blue
mem: 400
vcpus: 2
autostart: False

60 changes: 60 additions & 0 deletions packages/split-ssh/src/split-ssh-formula/pillar/init.jinja
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
## Load config
#{% load_yaml as config %}
#{% include 'split-ssh/config.yaml' %}
#{% endload %}
#
Comment on lines +1 to +5
Copy link
Owner Author

@gonzalo-bulnes gonzalo-bulnes Feb 27, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This template must yield valid YAML. A simple way to prevent the output of these Jinja statements and expressions from breaking the YAML is to comment them (#). These YAML comments do not affect the Jinja pre-processor.

## Collect information about the client(s)
#{% set client_names = [] %}
#{% set client_templates = [] %}
#
#{% for client in config.clients %}
#{{ client_names.append(client.name) }}
#{{ client_templates.append(client.template) }}
#{% endfor %}
#
## Collect information about the vault(s)
#{% set vault_names = [] %}
#{% set vault_templates = [] %}
#
#{% for vault in config.vaults %}
#{{ vault_names.append(vault.name) }}
#{{ vault_templates.append(vault.template) }}
#{% endfor %}
#
## Deduplicate and sort the lists to allow comparisons
## and ensure consistency
#{% set client_names = client_names|unique|sort %}
#{% set client_templates = client_templates|unique|sort %}
#{% set vault_names = vault_names|unique|sort %}
#{% set vault_templates = vault_templates|unique|sort %}
---
user:
dom0:
- split-ssh.clients
- split-ssh.vaults

{# Prevent duplicate keys error when client and vault templates are the same #}
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Salt doesn't tolerate duplicate keys in mappings. That could happen if the list of vault templates were identical to the list of client templates (e.g. both share the same fedora-32 template, it is actually a common scenario). Conveniently, in that case both the vault-template and client-template roles can be assigned at once.

{% if client_templates == vault_templates %}
{{ client_templates|join(',') }}:
- match: list
- split-ssh.client-template
- split-ssh.vault-template
{% else %}
{{ client_templates|join(',') }}:
- match: list
- split-ssh.client-template

{{ vault_templates|join(',') }}:
- match: list
- split-ssh.vault-template
{% endif %}

{# The clients and vaults themselves won't be the same qubes (split-SSH) #}
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are Jinja comments because they wouldn't be of any use in the resulting YAML file.


{{ client_names|join(',') }}:
- match: list
- split-ssh.client

{{ vault_names|join(',') }}:
- match: list
- split-ssh.vault
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
split-ssh-role:
vault-template: True
2 changes: 2 additions & 0 deletions packages/split-ssh/src/split-ssh-formula/pillar/vault.sls
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
split-ssh-role:
vault: True
7 changes: 7 additions & 0 deletions packages/split-ssh/src/split-ssh-formula/pillar/vaults.sls
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
## Load config
#{% load_yaml as config %}
#{% include 'split-ssh/config.yaml' %}
#{% endload %}
---
split-ssh-vaults:
{{ config.vaults }}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ make install DESTDIR=%{buildroot}
%files
%license LICENSE
%doc README.md
%config /srv/user_pillar/config.yaml
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Allows the configuration file to be kept when the package is updated.
See http://ftp.rpm.org/max-rpm/s1-rpm-inside-files-list-directives.html

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For future reference: the correct path is /srv/user_pillar/split-ssh/config.yaml (oops 🙄) see f3ea644

/srv/user_pillar/split-ssh
/srv/user_salt/split-ssh

%changelog
Expand Down
6 changes: 4 additions & 2 deletions packages/split-ssh/src/split-ssh-formula/state/client.top
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ user:
dom0:
- split-ssh.client.vm

fedora-32:
'split-ssh-role:client-template':
- match: pillar
Comment on lines +5 to +6
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assigning roles in the pillar allows to match qubes without relying on Salt grains.

- split-ssh.client.packages

ssh-client:
'split-ssh-role:client':
- match: pillar
- split-ssh.client.sock
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# Ensure the SSH socket discoverability
#
# See /srv/salt/split-ssh in dom0 for details.
SSH_VAULT_VM="ssh-vault"
SSH_VAULT_VM="{{ pillar.split-ssh-vaults|first }}"
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a current limitation: even if multiple vaults were defined in the configuration (/srv/user_pillar/config.yaml), all the clients would be configured to use the first vault in the list.

I didn't find any convenient way to specify which client should use which vault, and I am OK with this limitation until someone actually needs to use multiple vaults.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For future reference: the correct syntax is (pillar.get('split-ssh-vaults') and the value must be made available in the clients for it to be accessible (in the top file: split-ssh/init.jinja). See f3ea644


if [[ "$SSH_VAULT_VM" != "" ]]; then
export SSH_AUTH_SOCK=~user/.SSH_AGENT_$SSH_VAULT_VM
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# Create split-SSH socket
#
# See /srv/salt/split-ssh in dom0 for details.
SSH_VAULT_VM="ssh-vault"
SSH_VAULT_VM="{{ pillar.split-ssh-vaults|first }}"

if [[ "$SSH_VAULT_VM" != "" ]]; then
export SSH_SOCK=~user/.SSH_AGENT_$SSH_VAULT_VM
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
ssh-socket-present:
file.append:
- name: /rw/config/rc.local
- source: salt://split-ssh/client/files/rc.local.d/sock
- source: salt://split-ssh/client/files/rc.local.d/sock.jinja
- template: jinja

ssh-socket-discoverable:
file.append:
- name: ~user/.bashrc
- source: salt://split-ssh/client/files/bashrc.d/sock
- source: salt://split-ssh/client/files/bashrc.d/sock.jinja
- template: jinja
19 changes: 13 additions & 6 deletions packages/split-ssh/src/split-ssh-formula/state/client/vm.sls
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
ssh-client-present:
{% for client in pillar.split-ssh-clients %}
{{ client.name }}-present:
qvm.present:
- name: ssh-client
- template: fedora-32
- label: blue
- mem: 400
- vcpus: 2
- name: {{ client.name }}
- template: {{ client.template }}
- label: {{ client.label }}
- mem: {{ client.mem }}
- vcpus: {{ client.vcpus }}

{{ client.name }}-autostarts:
qvm.prefs:
- name: {{ client.name }}
- autostart: {{ client.autostart }}
{% endfor %}

Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
ssh-client ssh-vault ask
{% for vault in pillar.split-ssh-vault %}
{% for client in pillar.split-ssh-client %}
{{ client.name }} {{ vault.name }} ask
{% endfor %}
{% endfor %}
Comment on lines +1 to +5
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All clients can ask any of the vaults for SSH keys.

The default behavior (ask or allow) could be configured, but I don't see the need for now.

Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@
- mode: '0755'
- makedirs: True
- source: salt://split-ssh/policy/files/qubes.SSHAgent
- template: jinja
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the default for file.managed, but I think being explicit makes it clearer.


6 changes: 4 additions & 2 deletions packages/split-ssh/src/split-ssh-formula/state/vault.top
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ user:
dom0:
- split-ssh.vault.vm

fedora-32:
'split-ssh-role:vault-template':
- match: pillar
- split-ssh.vault.packages

ssh-vault:
'split-ssh-role:vault':
- match: pillar
- split-ssh.vault.config
- split-ssh.vault.rpc
Loading