mirror of
https://github.com/Mic92/sops-nix.git
synced 2024-12-14 11:57:52 +00:00
Allow to set uid and gid instead of owner and group. No checks will be performed when uid and gid are set.
``` sops.secrets = { sslCertificate = { sopsFile = ./secrets.yaml; owner = ""; group = ""; uid = config.containers."nginx".config.users.users."nginx".uid; gid = config.containers."nginx".config.users.groups."nginx".gid; }; sslCertificateKey = { sopsFile = ./secrets.yaml; owner = ""; group = ""; uid = config.containers."nginx".config.users.users."nginx".uid; gid = config.containers."nginx".config.users.groups."nginx".gid; }; }; ``` Co-authored-by: Jörg Thalheim <Mic92@users.noreply.github.com>
This commit is contained in:
parent
26642e8f19
commit
a4c33bfecb
5 changed files with 132 additions and 44 deletions
|
@ -73,18 +73,32 @@ let
|
|||
'';
|
||||
};
|
||||
owner = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "root";
|
||||
type = with lib.types; nullOr str;
|
||||
default = null;
|
||||
description = ''
|
||||
User of the file.
|
||||
User of the file. Can only be set if uid is 0.
|
||||
'';
|
||||
};
|
||||
uid = lib.mkOption {
|
||||
type = with lib.types; nullOr int;
|
||||
default = 0;
|
||||
description = ''
|
||||
UID of the file, only applied when owner is null. The UID will be applied even if the corresponding user doesn't exist.
|
||||
'';
|
||||
};
|
||||
group = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = users.${config.owner}.group;
|
||||
type = with lib.types; nullOr str;
|
||||
default = if config.owner != null then users.${config.owner}.group else null;
|
||||
defaultText = lib.literalMD "{option}`config.users.users.\${owner}.group`";
|
||||
description = ''
|
||||
Group of the file.
|
||||
Group of the file. Can only be set if gid is 0.
|
||||
'';
|
||||
};
|
||||
gid = lib.mkOption {
|
||||
type = with lib.types; nullOr int;
|
||||
default = 0;
|
||||
description = ''
|
||||
GID of the file, only applied when group is null. The GID will be applied even if the corresponding group doesn't exist.
|
||||
'';
|
||||
};
|
||||
sopsFile = lib.mkOption {
|
||||
|
@ -318,6 +332,12 @@ in {
|
|||
builtins.isPath secret.sopsFile ||
|
||||
(builtins.isString secret.sopsFile && lib.hasPrefix builtins.storeDir secret.sopsFile);
|
||||
message = "'${secret.sopsFile}' is not in the Nix store. Either add it to the Nix store or set sops.validateSopsFiles to false";
|
||||
} {
|
||||
assertion = secret.uid != null && secret.uid != 0 -> secret.owner == null;
|
||||
message = "In ${secret.name} exactly one of sops.owner and sops.uid must be set";
|
||||
} {
|
||||
assertion = secret.gid != null && secret.gid != 0 -> secret.group == null;
|
||||
message = "In ${secret.name} exactly one of sops.group and sops.gid must be set";
|
||||
}]) cfg.secrets)
|
||||
);
|
||||
|
||||
|
|
|
@ -43,7 +43,7 @@ in
|
|||
};
|
||||
|
||||
assertions = [{
|
||||
assertion = (lib.filterAttrs (_: v: v.owner != "root" || v.group != "root") secretsForUsers) == { };
|
||||
assertion = (lib.filterAttrs (_: v: (v.uid != 0 && v.owner != "root") || (v.gid != 0 && v.group != "root")) secretsForUsers) == { };
|
||||
message = "neededForUsers cannot be used for secrets that are not root-owned";
|
||||
} {
|
||||
assertion = secretsForUsers != { } && sysusersEnabled -> config.users.mutableUsers;
|
||||
|
|
|
@ -29,8 +29,10 @@ type secret struct {
|
|||
Name string `json:"name"`
|
||||
Key string `json:"key"`
|
||||
Path string `json:"path"`
|
||||
Owner string `json:"owner"`
|
||||
Group string `json:"group"`
|
||||
Owner *string `json:"owner,omitempty"`
|
||||
UID int `json:"uid"`
|
||||
Group *string `json:"group,omitempty"`
|
||||
GID int `json:"gid"`
|
||||
SopsFile string `json:"sopsFile"`
|
||||
Format FormatType `json:"format"`
|
||||
Mode string `json:"mode"`
|
||||
|
@ -475,25 +477,34 @@ func (app *appContext) validateSecret(secret *secret) error {
|
|||
secret.group = 0
|
||||
} else if app.checkMode == Off || app.ignorePasswd {
|
||||
// we only access to the user/group during deployment
|
||||
owner, err := user.Lookup(secret.Owner)
|
||||
|
||||
if secret.Owner == nil {
|
||||
secret.owner = secret.UID
|
||||
} else {
|
||||
owner, err := user.Lookup(*secret.Owner)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to lookup user '%s': %w", secret.Owner, err)
|
||||
return fmt.Errorf("failed to lookup user '%s': %w", *secret.Owner, err)
|
||||
}
|
||||
ownerNr, err := strconv.ParseUint(owner.Uid, 10, 64)
|
||||
uid, err := strconv.ParseUint(owner.Uid, 10, 64)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot parse uid %s: %w", owner.Uid, err)
|
||||
}
|
||||
secret.owner = int(ownerNr)
|
||||
|
||||
group, err := user.LookupGroup(secret.Group)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to lookup group '%s': %w", secret.Group, err)
|
||||
secret.owner = int(uid)
|
||||
}
|
||||
groupNr, err := strconv.ParseUint(group.Gid, 10, 64)
|
||||
|
||||
if secret.Group == nil {
|
||||
secret.group = secret.GID
|
||||
} else {
|
||||
group, err := user.LookupGroup(*secret.Group)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to lookup group '%s': %w", *secret.Group, err)
|
||||
}
|
||||
gid, err := strconv.ParseUint(group.Gid, 10, 64)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot parse gid %s: %w", group.Gid, err)
|
||||
}
|
||||
secret.group = int(groupNr)
|
||||
secret.group = int(gid)
|
||||
}
|
||||
}
|
||||
|
||||
if secret.Format == "" {
|
||||
|
|
|
@ -98,12 +98,14 @@ func testGPG(t *testing.T) {
|
|||
}
|
||||
}()
|
||||
|
||||
nobody := "nobody"
|
||||
nogroup := "nogroup"
|
||||
// should create a symlink
|
||||
yamlSecret := secret{
|
||||
Name: "test",
|
||||
Key: "test_key",
|
||||
Owner: "nobody",
|
||||
Group: "nogroup",
|
||||
Owner: &nobody,
|
||||
Group: &nogroup,
|
||||
SopsFile: path.Join(assets, "secrets.yaml"),
|
||||
Path: path.Join(testdir.path, "test-target"),
|
||||
Mode: "0400",
|
||||
|
@ -112,12 +114,13 @@ func testGPG(t *testing.T) {
|
|||
}
|
||||
|
||||
var jsonSecret, binarySecret, dotenvSecret, iniSecret secret
|
||||
root := "root"
|
||||
// should not create a symlink
|
||||
jsonSecret = yamlSecret
|
||||
jsonSecret.Name = "test2"
|
||||
jsonSecret.Owner = "root"
|
||||
jsonSecret.Owner = &root
|
||||
jsonSecret.Format = "json"
|
||||
jsonSecret.Group = "root"
|
||||
jsonSecret.Group = &root
|
||||
jsonSecret.SopsFile = path.Join(assets, "secrets.json")
|
||||
jsonSecret.Path = path.Join(testdir.secretsPath, "test2")
|
||||
jsonSecret.Mode = "0700"
|
||||
|
@ -130,16 +133,16 @@ func testGPG(t *testing.T) {
|
|||
|
||||
dotenvSecret = yamlSecret
|
||||
dotenvSecret.Name = "test4"
|
||||
dotenvSecret.Owner = "root"
|
||||
dotenvSecret.Group = "root"
|
||||
dotenvSecret.Owner = &root
|
||||
dotenvSecret.Group = &root
|
||||
dotenvSecret.Format = "dotenv"
|
||||
dotenvSecret.SopsFile = path.Join(assets, "secrets.env")
|
||||
dotenvSecret.Path = path.Join(testdir.secretsPath, "test4")
|
||||
|
||||
iniSecret = yamlSecret
|
||||
iniSecret.Name = "test5"
|
||||
iniSecret.Owner = "root"
|
||||
iniSecret.Group = "root"
|
||||
iniSecret.Owner = &root
|
||||
iniSecret.Group = &root
|
||||
iniSecret.Format = "ini"
|
||||
iniSecret.SopsFile = path.Join(assets, "secrets.ini")
|
||||
iniSecret.Path = path.Join(testdir.secretsPath, "test5")
|
||||
|
@ -214,11 +217,13 @@ func testSSHKey(t *testing.T) {
|
|||
ok(t, err)
|
||||
file.Close()
|
||||
|
||||
nobody := "nobody"
|
||||
nogroup := "nogroup"
|
||||
s := secret{
|
||||
Name: "test",
|
||||
Key: "test_key",
|
||||
Owner: "nobody",
|
||||
Group: "nogroup",
|
||||
Owner: &nobody,
|
||||
Group: &nogroup,
|
||||
SopsFile: path.Join(assets, "secrets.yaml"),
|
||||
Path: target,
|
||||
Mode: "0400",
|
||||
|
@ -247,11 +252,13 @@ func TestAge(t *testing.T) {
|
|||
ok(t, err)
|
||||
file.Close()
|
||||
|
||||
nobody := "nobody"
|
||||
nogroup := "nogroup"
|
||||
s := secret{
|
||||
Name: "test",
|
||||
Key: "test_key",
|
||||
Owner: "nobody",
|
||||
Group: "nogroup",
|
||||
Owner: &nobody,
|
||||
Group: &nogroup,
|
||||
SopsFile: path.Join(assets, "secrets.yaml"),
|
||||
Path: target,
|
||||
Mode: "0400",
|
||||
|
@ -280,11 +287,13 @@ func TestAgeWithSSH(t *testing.T) {
|
|||
ok(t, err)
|
||||
file.Close()
|
||||
|
||||
nobody := "nobody"
|
||||
nogroup := "nogroup"
|
||||
s := secret{
|
||||
Name: "test",
|
||||
Key: "test_key",
|
||||
Owner: "nobody",
|
||||
Group: "nogroup",
|
||||
Owner: &nobody,
|
||||
Group: &nogroup,
|
||||
SopsFile: path.Join(assets, "secrets.yaml"),
|
||||
Path: target,
|
||||
Mode: "0400",
|
||||
|
@ -314,11 +323,13 @@ func TestValidateManifest(t *testing.T) {
|
|||
testdir := newTestDir(t)
|
||||
defer testdir.Remove()
|
||||
|
||||
nobody := "nobody"
|
||||
nogroup := "nogroup"
|
||||
s := secret{
|
||||
Name: "test",
|
||||
Key: "test_key",
|
||||
Owner: "nobody",
|
||||
Group: "nogroup",
|
||||
Owner: &nobody,
|
||||
Group: &nogroup,
|
||||
SopsFile: path.Join(assets, "secrets.yaml"),
|
||||
Path: path.Join(testdir.path, "test-target"),
|
||||
Mode: "0400",
|
||||
|
|
|
@ -112,12 +112,41 @@ in {
|
|||
|
||||
age-keys = testers.runNixOSTest {
|
||||
name = "sops-age-keys";
|
||||
nodes.machine = { lib, ... }: {
|
||||
nodes.machine = { config, ... }: {
|
||||
imports = [ ../../modules/sops ];
|
||||
sops = {
|
||||
age.keyFile = "/run/age-keys.txt";
|
||||
defaultSopsFile = ./test-assets/secrets.yaml;
|
||||
secrets.test_key = { };
|
||||
secrets = {
|
||||
test_key = { };
|
||||
|
||||
test_key_someuser_somegroup = {
|
||||
uid = config.users.users."someuser".uid;
|
||||
gid = config.users.groups."somegroup".gid;
|
||||
key = "test_key";
|
||||
};
|
||||
test_key_someuser_root = {
|
||||
uid = config.users.users."someuser".uid;
|
||||
key = "test_key";
|
||||
};
|
||||
test_key_root_root = {
|
||||
key = "test_key";
|
||||
};
|
||||
test_key_1001_1001 = {
|
||||
uid = 1001;
|
||||
gid = 1001;
|
||||
key = "test_key";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
users.users."someuser" = {
|
||||
uid = 1000;
|
||||
group = "somegroup";
|
||||
isNormalUser = true;
|
||||
};
|
||||
users.groups."somegroup" = {
|
||||
gid = 1000;
|
||||
};
|
||||
|
||||
# must run before sops sets up keys
|
||||
|
@ -130,6 +159,22 @@ in {
|
|||
testScript = ''
|
||||
start_all()
|
||||
machine.succeed("cat /run/secrets/test_key | grep -q test_value")
|
||||
|
||||
with subtest("test ownership"):
|
||||
machine.succeed("[ $(stat -c%u /run/secrets/test_key_someuser_somegroup) = '1000' ]")
|
||||
machine.succeed("[ $(stat -c%g /run/secrets/test_key_someuser_somegroup) = '1000' ]")
|
||||
machine.succeed("[ $(stat -c%U /run/secrets/test_key_someuser_somegroup) = 'someuser' ]")
|
||||
machine.succeed("[ $(stat -c%G /run/secrets/test_key_someuser_somegroup) = 'somegroup' ]")
|
||||
|
||||
machine.succeed("[ $(stat -c%u /run/secrets/test_key_someuser_root) = '1000' ]")
|
||||
machine.succeed("[ $(stat -c%g /run/secrets/test_key_someuser_root) = '0' ]")
|
||||
machine.succeed("[ $(stat -c%U /run/secrets/test_key_someuser_root) = 'someuser' ]")
|
||||
machine.succeed("[ $(stat -c%G /run/secrets/test_key_someuser_root) = 'root' ]")
|
||||
|
||||
machine.succeed("[ $(stat -c%u /run/secrets/test_key_1001_1001) = '1001' ]")
|
||||
machine.succeed("[ $(stat -c%g /run/secrets/test_key_1001_1001) = '1001' ]")
|
||||
machine.succeed("[ $(stat -c%U /run/secrets/test_key_1001_1001) = 'UNKNOWN' ]")
|
||||
machine.succeed("[ $(stat -c%G /run/secrets/test_key_1001_1001) = 'UNKNOWN' ]")
|
||||
'';
|
||||
};
|
||||
|
||||
|
@ -142,6 +187,7 @@ in {
|
|||
type = "ed25519";
|
||||
path = ./test-assets/ssh-ed25519-key;
|
||||
}];
|
||||
|
||||
sops = {
|
||||
defaultSopsFile = ./test-assets/secrets.yaml;
|
||||
secrets.test_key = { };
|
||||
|
@ -161,7 +207,7 @@ in {
|
|||
|
||||
pgp-keys = testers.runNixOSTest {
|
||||
name = "sops-pgp-keys";
|
||||
nodes.server = { pkgs, lib, config, ... }: {
|
||||
nodes.server = { lib, config, ... }: {
|
||||
imports = [ ../../modules/sops ];
|
||||
|
||||
users.users.someuser = {
|
||||
|
|
Loading…
Reference in a new issue