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

implement sops decrypt --extract for lookup plugin #200

Merged
merged 11 commits into from
Aug 24, 2024
8 changes: 8 additions & 0 deletions changelogs/changelog.yaml
niuleh marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -407,3 +407,11 @@ releases:
- 1.8.2.yml
- deprecate-eol-ansible-core.yml
release_date: '2024-08-13'
1.9.0:
changes:
minor_changes:
- sops lookup plugin - new option ``extract`` allows extracting a single
key out of a JSON or YAML file, equivalent to sops' ``decrypt --extract``.
fragments:
- 1.9.0.yml
- 200-lookup-extract.yaml
12 changes: 10 additions & 2 deletions plugins/lookup/sops.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,13 @@
but return an empty string instead.
type: bool
default: false
extract:
description:
- Tell SOPS to extract a specific key from a JSON or YAML file.
# - Expects the same 'query' syntax as SOPS' --encrypt option,
# e.g. '["somekey"][0]'. Note: all quotes are mandatory and must be escaped appropriately.
niuleh marked this conversation as resolved.
Show resolved Hide resolved
type: str
niuleh marked this conversation as resolved.
Show resolved Hide resolved
version_added: 1.9.0
extends_documentation_fragment:
- community.sops.sops
- community.sops.sops.ansible_variables
Expand Down Expand Up @@ -126,6 +133,7 @@ def run(self, terms, variables=None, **kwargs):
input_type = self.get_option('input_type')
output_type = self.get_option('output_type')
empty_on_not_exist = self.get_option('empty_on_not_exist')
extract = self.get_option('extract')

ret = []

Expand All @@ -145,8 +153,8 @@ def get_option_value(argument_name):

try:
output = Sops.decrypt(
lookupfile, display=display, rstrip=rstrip, decode_output=not use_base64,
input_type=input_type, output_type=output_type, get_option_value=get_option_value)
lookupfile, display=display, rstrip=rstrip, decode_output=(not use_base64),
input_type=input_type, output_type=output_type, get_option_value=get_option_value, extract=extract)
except SopsError as e:
raise AnsibleLookupError(to_native(e))

Expand Down
9 changes: 6 additions & 3 deletions plugins/module_utils/sops.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ def f(value, arguments_pre, arguments_post, env, version):
env[argument_name] = value

return f

niuleh marked this conversation as resolved.
Show resolved Hide resolved

GENERAL_OPTIONS = {
'age_key': _create_env_variable('SOPS_AGE_KEY'),
Expand Down Expand Up @@ -191,7 +191,7 @@ def _run_command(self, command, env=None, data=None, cwd=None):
return process.returncode, output, err

def decrypt(self, encrypted_file, content=None,
decode_output=True, rstrip=True, input_type=None, output_type=None, get_option_value=None):
decode_output=True, rstrip=True, input_type=None, output_type=None, get_option_value=None, extract=None):
# Run sops directly, python module is deprecated
command = [self.binary]
command_post = []
Expand All @@ -206,6 +206,8 @@ def decrypt(self, encrypted_file, content=None,
command.extend(["--output-type", output_type])
if self.version < (3, 9, 0):
command.append("--decrypt")
if extract is not None:
command.extend(["--extract", extract])
if content is not None:
encrypted_file = '/dev/stdin'
command.append(encrypted_file)
Expand Down Expand Up @@ -316,7 +318,7 @@ def get_sops_runner_from_options(get_option_value, module=None, display=None):

@staticmethod
def decrypt(encrypted_file, content=None,
display=None, decode_output=True, rstrip=True, input_type=None, output_type=None, get_option_value=None, module=None):
display=None, decode_output=True, rstrip=True, input_type=None, output_type=None, get_option_value=None, module=None, extract=None):
runner = Sops.get_sops_runner_from_options(get_option_value, module=module, display=display)
return runner.decrypt(
encrypted_file,
Expand All @@ -326,6 +328,7 @@ def decrypt(encrypted_file, content=None,
input_type=input_type,
output_type=output_type,
get_option_value=get_option_value,
extract=extract,
)

@staticmethod
Expand Down
29 changes: 29 additions & 0 deletions tests/integration/targets/lookup_sops/files/extract.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"foo": "ENC[AES256_GCM,data:yi6Y,iv:Eflygn9pqdyIXQisSPar0rOpdEScU7qo3ux8NDDyhpU=,tag:HwKdjMJaA/PW4N0nyFh/hA==,type:str]",
"bar": "ENC[AES256_GCM,data:nccS,iv:U41nqqAV2VpgW9R9rnxxRzN5SRkdvCNd58OQhhuaZYQ=,tag:dvlOpTuZvfvbcfD8HD7zvQ==,type:str]",
"baz": {
"bar": [
"ENC[AES256_GCM,data:CRMt,iv:6s5PcOsmqzNwGSyNGjxPDp1/cTXjp5QCXLC1717xAAw=,tag:NTyJsPgIQzBtTG2Sp4H4PQ==,type:str]",
"ENC[AES256_GCM,data:zIZJ,iv:KY4ABl0WieDmYEKUGfeAnDbA8/V79YpWg2bcrwUh0+E=,tag:4je4L70Fc0OmaOlCTjBedg==,type:str]",
"ENC[AES256_GCM,data:dbbZ,iv:BQVW4cEMnX+IFz3SXdenlDZv4o1iwpB+ZuFKV6qE0OI=,tag:kdb/8TNjoVvLiw3kna7okg==,type:str]"
]
},
"sops": {
"kms": null,
"gcp_kms": null,
"azure_kv": null,
"hc_vault": null,
"age": null,
"lastmodified": "2024-08-20T19:29:14Z",
"mac": "ENC[AES256_GCM,data:r8ECKCCtb6A3db1SXoaIFS/wTSOvab4Ui5GDHu0CXikCluGOqpWWHaVccdBIG5myvzSJPiLmxuFCaciiAjl4fQJCqrR4cau9Via2Kx7rbQzmXWsqXTCry/ISeqdKS4oQ5UL3+YcPq7mN3zkC8UIrDhc4CCbp9rFYqIKQp8xqnqo=,iv:wU7+lDSdQzfFfCudyfgCagVpmXBIRI1/gVSI/bUxpGk=,tag:kUPCPNkfhfc1ipzYC/sLaw==,type:str]",
"pgp": [
{
"created_at": "2024-08-20T19:27:56Z",
"enc": "-----BEGIN PGP MESSAGE-----\n\nhQEMAyUpShfNkFB/AQf/eMDcnG0qtwov/cfFAqUv9EE7RmNvipHB2hnnjO2fT1ud\nMZeiBbIZ3R5wWkZFsVHNO+pb3cv+txDDy2e8Td3FHQNZOqJ889D1reeXVVkHnMJ2\nwVJgy91m2f8eERmiRQO+jwa2UFPx80dI84ve8WT3jnxNyvxtrdm14WYNfv5ZOfte\n9IDnpv4YQZQj8BYh+Ag1//bRG7i8OcwBMAngMHbeYrW5WmHB6AH8PwJCqag7wA+5\nu7oRA0VppyIYrGUSogxcaOhhYBGcMVJxzvpIQvlGGC7SSXl4LcuRChRP/pCT5kQy\n44RQXnQTFwJR/l4RXixcQ3fqqgVMj/sOIyOgCFCegtJeAdEYCxDH59fomb5ejvuA\n96G0WlILPirSjvdECOQna/2XFcnKOPUPur0Rn/y9TUN6pIVaCeoJH+Fj6Y4P3Gk2\nJqWWYELsgKKmyf798Nv2yoTts2TYUEOZ374Y3F0HYQ==\n=RgW1\n-----END PGP MESSAGE-----",
"fp": "FBC7B9E2A4F9289AC0C1D4843D16CEE4A27381B4"
}
],
"unencrypted_suffix": "_unencrypted",
"version": "3.9.0"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
SPDX-License-Identifier: GPL-3.0-or-later
SPDX-FileCopyrightText: Ansible Project
33 changes: 33 additions & 0 deletions tests/integration/targets/lookup_sops/files/extract.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
foo: ENC[AES256_GCM,data:Ping,iv:+Ehv7ZgqHiqeZfx98xzZskOS31i53AXq79N7OOlCz3Y=,tag:+dhNWa+Se0xPLIbs2OI4OQ==,type:str]
bar: ENC[AES256_GCM,data:CAdf,iv:JzgxIFcLl9KKP34GfMsZtemIrW8UJOATdML2jTJByak=,tag:X5LI0mDNep1+dfGifRm/DQ==,type:str]
baz:
bar:
- ENC[AES256_GCM,data:sCC+,iv:n+Kz+sYPQckZfAiyWYsMde5q7kXCcJcrj6dTcmyTZIg=,tag:8EfSOibdo4W0kDA60iVJoQ==,type:str]
- ENC[AES256_GCM,data:ohn1,iv:nSIRsVG1OsOL9tqhswJAMPjqHI/TjX7kU8AnBPwFUMM=,tag:QR1Ja0IRis+mwgtqcMeoVg==,type:str]
- ENC[AES256_GCM,data:Bnl0,iv:SVGDhCFup2yeu01pvFMIJPmfmRYmWXdroWF9x5U8WQ4=,tag:lLVSoS8hHW6Yu8RPIcWa3w==,type:str]
sops:
kms: []
gcp_kms: []
azure_kv: []
hc_vault: []
age: []
lastmodified: "2024-08-20T19:27:52Z"
mac: ENC[AES256_GCM,data:k1k3lpvr/KN0eaM3Fvf1lECjw1pUym56OY0WMwcGnEyM5SadE8t2LQ3nLd6CLoSqkZSlhmXZdtt2vMnE1gJzfvQ29joKd2GVwEKe0KVHzrwKEaHix48x0WAO7EYgk6g6jn+VFv0GeESZKOpn8niduOAFjr9Fz04UMyyQiC813M4=,iv:F3uwk6/nQSkHDMQSv3roXew3zkyAiL/I2CudS/Bs8ds=,tag:SyA6fZlBM0XLVTk8U5JIQQ==,type:str]
pgp:
- created_at: "2024-08-20T19:27:21Z"
enc: |-
-----BEGIN PGP MESSAGE-----

hQEMAyUpShfNkFB/AQf/YNyhkK7Dws/fuu4TnlzNm21HDbZu5Vbv/l8alB1QJelM
NbrtyOnG1SscUoWR567A7SMlXAHIGMJnyNz5rdL1Csl82wY9EHBV3QLy7zhrOvsz
GWzs5YFpT4YURzUDqOHuFTodmNQL1J/8/81ROHrH9aV2TdXmFRa4iBjqSvzK45As
xMuIV0AY4X54zRwPLXjnS8xlYQZfEY3dpRFeje2X92JsMYo8JZpgw1eVbzXc3+sU
pJfQZdiJJYUft7Zxz+5kjXSea3xBPOvoZOCDmFLjSv5nzo9dimmqek+S0rQGUfz7
LCcD43uz7KyZneGqrNwx7M2HW8dkH4izUXnDa/EqYNJRAS3X6AWklvBcAsoA20U1
i6UJr88mSJKExlZGjyPeTrSr2VBfbebnO+gxPMUK1+TI4qnr5cLwP1TvTOzuJjKw
CZUGPN6bA2CrGSgqZ8QsIxVB
=8HdX
-----END PGP MESSAGE-----
fp: FBC7B9E2A4F9289AC0C1D4843D16CEE4A27381B4
unencrypted_suffix: _unencrypted
version: 3.9.0
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
SPDX-License-Identifier: GPL-3.0-or-later
SPDX-FileCopyrightText: Ansible Project
16 changes: 16 additions & 0 deletions tests/integration/targets/lookup_sops/tasks/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,22 @@
- "sops_lookup_simple is success"
- "sops_success == 'foo: bar'"

- name: Test extract
set_fact:
simple_yaml_with_extract: "{{ lookup('community.sops.sops', 'extract.yaml', extract=\"['foo']\") }}"
simple_json_with_extract: "{{ lookup('community.sops.sops', 'extract.json', extract=\"['bar']\") }}"
nested_yaml_with_extract: "{{ lookup('community.sops.sops', 'extract.yaml', extract=\"['baz']['bar'][0]\") }}"
nested_json_with_extract: "{{ lookup('community.sops.sops', 'extract.json', extract=\"['baz']['bar'][2]\") }}"
niuleh marked this conversation as resolved.
Show resolved Hide resolved
register: sops_lookup_extract

- assert:
that:
- "sops_lookup_extract is success"
- "simple_yaml_with_extract == 'bar'"
- "simple_json_with_extract == 'baz'"
- "nested_yaml_with_extract == 'zab'"
- "nested_json_with_extract == 'oof'"

- name: Test rstrip
set_fact:
with_rstrip: "{{ lookup('community.sops.sops', 'rstrip.sops', rstrip=true) }}"
Expand Down
Loading