fix: multiple user keys in secrets
We were not loading all the user keys, only the first one.
This commit is contained in:
committed by
Michael Hoang
parent
1bfe318865
commit
e281b689df
@@ -201,7 +201,7 @@ def encrypt_secret(
|
|||||||
|
|
||||||
recipient_keys = collect_keys_for_path(secret_path)
|
recipient_keys = collect_keys_for_path(secret_path)
|
||||||
|
|
||||||
if not admin_keys.intersection(recipient_keys):
|
if admin_keys not in recipient_keys:
|
||||||
recipient_keys.update(admin_keys)
|
recipient_keys.update(admin_keys)
|
||||||
|
|
||||||
files_to_commit.extend(
|
files_to_commit.extend(
|
||||||
|
|||||||
@@ -360,7 +360,7 @@ def maybe_get_user(flake_dir: Path, key: SopsKey) -> set[SopsKey] | None:
|
|||||||
|
|
||||||
keys = read_keys(user)
|
keys = read_keys(user)
|
||||||
if key in keys:
|
if key in keys:
|
||||||
return {SopsKey(key.pubkey, user.name, key.key_type)}
|
return {SopsKey(key.pubkey, user.name, key.key_type) for key in keys}
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|||||||
@@ -62,6 +62,10 @@ KEYS = [
|
|||||||
"age1eyyhln9g3cdwtrwpckugvqgtf5p8ugt0426sw38ra3wkc0t4rfhslq7txv",
|
"age1eyyhln9g3cdwtrwpckugvqgtf5p8ugt0426sw38ra3wkc0t4rfhslq7txv",
|
||||||
"AGE-SECRET-KEY-1567QKA63Y9P62SHF5TCHVCT5GZX2LZ8NS0E9RKA2QHDA662SF5LQ2VJJYX",
|
"AGE-SECRET-KEY-1567QKA63Y9P62SHF5TCHVCT5GZX2LZ8NS0E9RKA2QHDA662SF5LQ2VJJYX",
|
||||||
),
|
),
|
||||||
|
KeyPair(
|
||||||
|
"age1e9ufa6wrsr5danka50qp0np0832uz7jca7s00wyeg2nt3aqnvaks7p4jfr",
|
||||||
|
"AGE-SECRET-KEY-1Z89SHU9KAF709TTAZDARUWKC7H9TPZW4L8A2PVYSYAF7QVLCNQZQZ07U5J",
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
import random
|
||||||
import re
|
import re
|
||||||
|
import string
|
||||||
from collections.abc import Iterator
|
from collections.abc import Iterator
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
@@ -162,23 +164,23 @@ def test_users(
|
|||||||
with monkeypatch.context():
|
with monkeypatch.context():
|
||||||
_test_identities("users", test_flake, capture_output, age_keys, monkeypatch)
|
_test_identities("users", test_flake, capture_output, age_keys, monkeypatch)
|
||||||
|
|
||||||
# some additional user-specific tests
|
|
||||||
|
|
||||||
admin_key = age_keys[2]
|
def test_multiple_user_keys(
|
||||||
|
test_flake: FlakeForTest,
|
||||||
|
capture_output: CaptureOutput,
|
||||||
|
age_keys: list["KeyPair"],
|
||||||
|
monkeypatch: pytest.MonkeyPatch,
|
||||||
|
) -> None:
|
||||||
sops_folder = test_flake.path / "sops"
|
sops_folder = test_flake.path / "sops"
|
||||||
|
|
||||||
user_keys = {
|
users_keys = {
|
||||||
"bob": [age_keys[0], age_keys[1]],
|
"bob": {age_keys[0], age_keys[1]},
|
||||||
"alice": [age_keys[2]],
|
"alice": {age_keys[2]},
|
||||||
"charlie": [age_keys[3], age_keys[4]],
|
"charlie": {age_keys[3], age_keys[4], age_keys[5]},
|
||||||
}
|
}
|
||||||
|
|
||||||
monkeypatch.setenv("SOPS_NIX_SECRET", "deadfeed")
|
for user, user_keys in users_keys.items():
|
||||||
|
# add the user
|
||||||
for user, keys in user_keys.items():
|
|
||||||
key_args = [f"--age-key={key.pubkey}" for key in keys]
|
|
||||||
|
|
||||||
# add the user keys
|
|
||||||
cli.run(
|
cli.run(
|
||||||
[
|
[
|
||||||
"secrets",
|
"secrets",
|
||||||
@@ -187,46 +189,70 @@ def test_users(
|
|||||||
"--flake",
|
"--flake",
|
||||||
str(test_flake.path),
|
str(test_flake.path),
|
||||||
user,
|
user,
|
||||||
*key_args,
|
*[f"--age-key={key.pubkey}" for key in user_keys],
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
assert (sops_folder / "users" / user / "key.json").exists()
|
assert (sops_folder / "users" / user / "key.json").exists()
|
||||||
|
|
||||||
# check they are returned in get
|
# check they are returned in get
|
||||||
with capture_output as output:
|
with capture_output as output:
|
||||||
cli.run(
|
cli.run(["secrets", "users", "get", "--flake", str(test_flake.path), user])
|
||||||
["secrets", "users", "get", "--flake", str(test_flake.path), user]
|
|
||||||
)
|
|
||||||
|
|
||||||
for key in keys:
|
for user_key in user_keys:
|
||||||
assert key.pubkey in output.out
|
assert user_key.pubkey in output.out
|
||||||
|
|
||||||
|
# let's do some setting and getting of secrets
|
||||||
|
|
||||||
|
def random_str() -> str:
|
||||||
|
return "".join(random.choices(string.ascii_letters, k=10))
|
||||||
|
|
||||||
|
for user_key in user_keys:
|
||||||
|
# set a secret using each of the user's private keys
|
||||||
|
with monkeypatch.context():
|
||||||
|
secret_name = f"{user}_secret_{random_str()}"
|
||||||
|
secret_value = random_str()
|
||||||
|
|
||||||
|
monkeypatch.setenv("SOPS_AGE_KEY", user_key.privkey)
|
||||||
|
monkeypatch.setenv("SOPS_NIX_SECRET", secret_value)
|
||||||
|
|
||||||
# set a secret
|
|
||||||
secret_name = f"{user}_secret"
|
|
||||||
cli.run(
|
cli.run(
|
||||||
[
|
[
|
||||||
"secrets",
|
"secrets",
|
||||||
"set",
|
"set",
|
||||||
"--flake",
|
"--flake",
|
||||||
str(test_flake.path),
|
str(test_flake.path),
|
||||||
"--user",
|
|
||||||
user,
|
|
||||||
secret_name,
|
secret_name,
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
# check the secret has each of our user's keys as a recipient
|
# check the secret has each of our user's keys as a recipient
|
||||||
# in addition the admin key should be there
|
|
||||||
assert_secrets_file_recipients(
|
assert_secrets_file_recipients(
|
||||||
test_flake.path,
|
test_flake.path,
|
||||||
secret_name,
|
secret_name,
|
||||||
expected_age_recipients_keypairs=[admin_key, *keys],
|
expected_age_recipients_keypairs=[*user_keys],
|
||||||
)
|
)
|
||||||
|
|
||||||
if len(keys) == 1:
|
# check we can get the secret
|
||||||
|
with capture_output as output:
|
||||||
|
cli.run(
|
||||||
|
["secrets", "get", "--flake", str(test_flake.path), secret_name]
|
||||||
|
)
|
||||||
|
|
||||||
|
assert secret_value in output.out
|
||||||
|
|
||||||
|
if len(user_keys) == 1:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# remove one of the keys
|
# remove one of the user keys,
|
||||||
|
user_keys_iter = iter(user_keys)
|
||||||
|
|
||||||
|
key_to_remove = next(user_keys_iter)
|
||||||
|
key_to_encrypt_with = next(user_keys_iter)
|
||||||
|
|
||||||
|
with monkeypatch.context():
|
||||||
|
monkeypatch.setenv("SOPS_AGE_KEY", key_to_encrypt_with.privkey)
|
||||||
|
monkeypatch.setenv("SOPS_NIX_SECRET", "deafbeef")
|
||||||
|
|
||||||
cli.run(
|
cli.run(
|
||||||
[
|
[
|
||||||
"secrets",
|
"secrets",
|
||||||
@@ -235,7 +261,7 @@ def test_users(
|
|||||||
"--flake",
|
"--flake",
|
||||||
str(test_flake.path),
|
str(test_flake.path),
|
||||||
user,
|
user,
|
||||||
keys[0].pubkey,
|
key_to_remove.pubkey,
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -243,7 +269,27 @@ def test_users(
|
|||||||
assert_secrets_file_recipients(
|
assert_secrets_file_recipients(
|
||||||
test_flake.path,
|
test_flake.path,
|
||||||
secret_name,
|
secret_name,
|
||||||
expected_age_recipients_keypairs=[admin_key, *keys[1:]],
|
expected_age_recipients_keypairs=list({*user_keys} - {key_to_remove}),
|
||||||
|
)
|
||||||
|
|
||||||
|
# add the key back
|
||||||
|
cli.run(
|
||||||
|
[
|
||||||
|
"secrets",
|
||||||
|
"users",
|
||||||
|
"add-key",
|
||||||
|
"--flake",
|
||||||
|
str(test_flake.path),
|
||||||
|
user,
|
||||||
|
key_to_remove.pubkey,
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
# check the secret has been updated
|
||||||
|
assert_secrets_file_recipients(
|
||||||
|
test_flake.path,
|
||||||
|
secret_name,
|
||||||
|
expected_age_recipients_keypairs=user_keys,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user