Skip to content

Add a integration where the sudo password can be specified in a sops encrypted file #324

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

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Open
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
27 changes: 27 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,11 @@ This is a set of options that can be put in any of the above definitions, with t
# This defaults to `false`
interactiveSudo = false;

# Whether to enable the sops integration for password based sudo on the remote host. Useful when using non-root sshUsers.
# This defaults to not beeing used.
sudoFile = ./path.yaml;
sudoSecret = "secret";

# This is an optional list of arguments that will be passed to SSH.
sshOpts = [ "-p" "2121" ];

Expand Down Expand Up @@ -238,6 +243,28 @@ This is a set of options that can be put in any of the above definitions, with t

Some of these options can be provided during `deploy` invocation to override default values or values provided in your flake, see `deploy --help`.

### Sudo on remote host

There are two different ways to supply a password for elevating privileges on the remote host, but only one can be used at a time.
The first is `interactiveSudo`, where the user will get prompted for a password while running the deployment.
The other option is to use sops to provide the secrets.

#### Sops

In order to use the [sops](https://github.com/getsops/sops) integration `sudoFile` as well as `sudoSecret` have to be specified for a node.
While running the deployment `sops` is used to decrypt the path `sudoFile` and search for `sudoSecret` within the file.
When specifying the `sudoSecret` you can address the key as specified below:

```yaml
password:
test: 123
password_test_user: abc
```

You can refer to the password `123` as `password/test` and `abc` as `password_test_user`.
Keep in mind that we only handle nested secrets with strings, numbers and boolean.
For an example please see the [sops example](./examples/sops).

## About Serokell

deploy-rs is maintained and funded with ❤️ by [Serokell](https://serokell.io/).
Expand Down
7 changes: 7 additions & 0 deletions examples/sops/.sops.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
keys:
- &primary age179s8jnppgy9kwakmva8av6frpnhgg9myrvk3xlfpanmhvvzyh96sdygfcm
creation_rules:
- path_regex: passwords.yaml
key_groups:
- age:
- *primary
18 changes: 18 additions & 0 deletions examples/sops/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Example NixOS system deployment where password is passed via sops

This is an example of how to use the sops integration for deploy-rs.

To decrypt the password manually use `SOPS_AGE_KEY_FILE=$(pwd)/age_private.txt sops -d passwords.yaml`.
Note that sops will try to search for the private key for age in `$XDG_CONFIG_HOME/sops/age/keys.txt` by default,
but this can be overridden with `SOPS_AGE_KEY_FILE`. For more information please see the [sops documentation](https://getsops.io/docs/#encrypting-using-age).

1. Run bare system from `.#nixosConfigurations.sops`

- `nix build .#nixosConfigurations.sops.config.system.build.vm`
- `QEMU_NET_OPTS=hostfwd=tcp::2221-:22 ./result/bin/run-sops-vm`
- you can manually ssh into the machine via `ssh deploy@localhost -p 2221 -i ./hello_ed25519`

2. Develop the devshell via `nix develop .` to get sops, age and `deploy` added to $PATH
3. Run via `deploy .` to deploy the "new" Configuration updated
4. ???
5. PROFIT!!!
3 changes: 3 additions & 0 deletions examples/sops/age_private.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# created: 2025-06-05T11:36:08+02:00
# public key: age179s8jnppgy9kwakmva8av6frpnhgg9myrvk3xlfpanmhvvzyh96sdygfcm
AGE-SECRET-KEY-1L8HTRL2THGGZLXQQDTDLDG0U8EL4RSSAMVT9RYUG5JWPUJW49N9QS0EFSZ
1 change: 1 addition & 0 deletions examples/sops/age_public.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
age179s8jnppgy9kwakmva8av6frpnhgg9myrvk3xlfpanmhvvzyh96sdygfcm
44 changes: 44 additions & 0 deletions examples/sops/configuration.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
{ pkgs, ... }:
{
networking.hostName = "sops";
nix.settings = {
# allow the `deploy` user to push unsigned NARs
allowed-users = [ "deploy" ];
trusted-users = [ "deploy" ];
};

# setup a user for the deployment
users.users.deploy = {
isNormalUser = true;
password = "heloWorld";
openssh.authorizedKeys.keys = [
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFnXmG3pSC8+UfmrHH0L5UtT++KqTmLp+1B3oWIJ1IBB hello@localhost"
];
extraGroups = [
"wheel"
"sudo"
]; # for sudo su
uid = 1010;
};

# setup the rest of the system
boot.loader = {
systemd-boot.enable = true;
efi.canTouchEfiVariables = true;
};

services.openssh.enable = true;

nix.settings = {
substituters = pkgs.lib.mkForce [ ];
experimental-features = "nix-command flakes";
trusted-public-keys = [ "cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY=" ];
};

# settings for the vm
virtualisation = {
useBootLoader = true;
writableStore = true;
useEFIBoot = true;
};
}
114 changes: 114 additions & 0 deletions examples/sops/flake.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

76 changes: 76 additions & 0 deletions examples/sops/flake.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
{
description = "Deploy a full system where the password is supplied via sops";

inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
deploy-rs.url = "github:weriomat/deploy-rs/sops";
};

outputs =
{
self,
nixpkgs,
deploy-rs,
...
}:
let
system = "x86_64-linux";
pkgs = import nixpkgs { inherit system; };
in
{
nixosConfigurations = {
sops = nixpkgs.lib.nixosSystem {
inherit system;
modules = [
./configuration.nix
(pkgs.path + "/nixos/modules/virtualisation/qemu-vm.nix")
];
};
updated = nixpkgs.lib.nixosSystem {
inherit system;
modules = [
(pkgs.path + "/nixos/modules/virtualisation/qemu-vm.nix")
./configuration.nix
./updated.nix
];
};
};

# packages we need to inspect the encrypted files
devShells.x86_64-linux.default = pkgs.mkShell {
buildInputs = [
deploy-rs.packages.default
pkgs.sops
pkgs.age
];
};

deploy.nodes.example = {
sshOpts = [
"-p"
"2221"
];
hostname = "localhost";
fastConnection = true;
sudoFile = ./passwords.yaml;

profiles.system = {
user = "root";
sshUser = "deploy";

# sudo password is gotten via
sudoSecret = "password/deploy";

# we setup ssh auth with this key, these will get merged with the settings above
sshOpts = [
"-i"
"./hello_ed25519"
];

path = deploy-rs.lib.x86_64-linux.activate.nixos self.nixosConfigurations.updated; # this is a bit hacky to get a "updated" configuration to deploy
};
};

checks = builtins.mapAttrs (system: deployLib: deployLib.deployChecks self.deploy) deploy-rs.lib;
};
}
7 changes: 7 additions & 0 deletions examples/sops/hello_ed25519
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
QyNTUxOQAAACBZ15ht6UgvPlH5qxx9C+VLU/viqk5i6ftQd6FiCdSAQQAAAJhLKSuiSykr
ogAAAAtzc2gtZWQyNTUxOQAAACBZ15ht6UgvPlH5qxx9C+VLU/viqk5i6ftQd6FiCdSAQQ
AAAECHxBqQ8m4mlSF5N83v6x2XxUZB1ao85TyroGq333v5v1nXmG3pSC8+UfmrHH0L5UtT
++KqTmLp+1B3oWIJ1IBBAAAAD2hlbGxvQGxvY2FsaG9zdAECAwQFBg==
-----END OPENSSH PRIVATE KEY-----
1 change: 1 addition & 0 deletions examples/sops/hello_ed25519.pub
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFnXmG3pSC8+UfmrHH0L5UtT++KqTmLp+1B3oWIJ1IBB hello@localhost
17 changes: 17 additions & 0 deletions examples/sops/passwords.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
password:
deploy: ENC[AES256_GCM,data:t8IlqzJr5v+A,iv:L3/+IQ6+gl/az3ya+rc/yFJ89vdjI6NvletIyhO5EcA=,tag:sCzIp1WvRlpLtlm/i1AXmw==,type:str]
sops:
age:
- recipient: age179s8jnppgy9kwakmva8av6frpnhgg9myrvk3xlfpanmhvvzyh96sdygfcm
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBTUjRhQ2pieU1XakR5eTFH
elpocXhFYUVBL3p4Z0RjcDhvcEZmNEtVWkNvCngvT21NNFpSNENwVVRsemROcGRN
aGJEVWhmWmI2dFQrRWw1RUhudElNMU0KLS0tIFYwUmQzLzVqVzdxSkdZTXIrMGVG
ZGZ4eS82bjZvWmxRRk1wbHFsZ2RlUGcKQeaupX894rGIal5ov0MOSaRVd4OQ7muQ
IkCtwZ+v2nn4xtd9MEdNur8z81civvCz907fmlKxtyk9NSLY8UP54w==
-----END AGE ENCRYPTED FILE-----
lastmodified: "2025-06-05T15:16:06Z"
mac: ENC[AES256_GCM,data:k7mANamp8envEFkAcsbbpvw3GoffaBBI4N7S90QFIX5bxdPsMQvQ9Lddh6nJAjYX0DGBUafVn3c2C1LXTJAmHbrLoqgn5uW18AHhNhnIt9M+5zXcT9olxoMt/aDd6OucCJ0N/vQ5fuD5HfFJwoANIg+2cEONfNoubspiI+K+Y/4=,iv:9GnnM7amxpmFGVpJ4q0pwJDFuldt5bAISrTxdJS9FbU=,tag:0AGdq2+jxQpjRv7zNIXFBg==,type:str]
unencrypted_suffix: _unencrypted
version: 3.10.2
17 changes: 17 additions & 0 deletions examples/sops/updated.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{ lib, ... }:
let
inherit (lib) mkForce;
in
{
# update some config
networking.hostName = mkForce "updated";
users = {
users.updated = {
isNormalUser = true;
password = "aReallyComplicatedPassword";
uid = 1011;
group = "updated";
};
groups.updated = { };
};
}
10 changes: 6 additions & 4 deletions flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@
in
{
deploy-rs = {

deploy-rs = final.rustPlatform.buildRustPackage (darwinOptions // {
pname = "deploy-rs";
version = "0.1.0";
Expand All @@ -40,6 +39,8 @@
".*\.rs$"
];

runtimeInputs = [ final.pkgs.sops ];

cargoLock.lockFile = ./Cargo.lock;
meta = {
description = "A Simple multi-profile Nix-flake deploy tool";
Expand Down Expand Up @@ -164,13 +165,13 @@
};
in
{
packages.default = self.packages."${system}".deploy-rs;
packages.default = self.packages.${system}.deploy-rs;
packages.deploy-rs = pkgs.deploy-rs.deploy-rs;

apps.default = self.apps."${system}".deploy-rs;
apps.default = self.apps.${system}.deploy-rs;
apps.deploy-rs = {
type = "app";
program = "${self.packages."${system}".default}/bin/deploy";
program = "${self.packages.${system}.default}/bin/deploy";
};

devShells.default = pkgs.mkShell {
Expand All @@ -184,6 +185,7 @@
rustfmt
clippy
reuse
sops
rust.packages.stable.rustPlatform.rustLibSrc
];
};
Expand Down
Loading