-
Notifications
You must be signed in to change notification settings - Fork 660
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add optional rule for no-same-owner (#1450)
Add a new rule that identifies use of the same owner when transferring files between hosts as a violation. This rule is disabled by default and user needs to manually activate it inside the config as it is needed only for very particular use-cases. Reference: https://zuul-ci.org/docs/zuul-jobs/policy.html#preservation-of-owner-between-executor-and-remote
- Loading branch information
Showing
9 changed files
with
251 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
- name: block | ||
block: | ||
- name: synchonize-in-block | ||
synchronize: | ||
src: dummy | ||
dest: dummy | ||
|
||
- name: synchronize | ||
synchronize: | ||
src: dummy | ||
dest: dummy | ||
|
||
- name: nested-block | ||
block: | ||
- block: | ||
- name: synchonize-in-deep-block | ||
synchronize: | ||
src: dummy | ||
dest: dummy | ||
|
||
- name: unarchive-bz2 | ||
unarchive: | ||
src: "{{ file }}.tar.bz2" | ||
dest: "dummy" | ||
|
||
- name: unarchive delegated | ||
unarchive: | ||
src: "{{ file }}.tar.bz2" | ||
dest: "dummy" | ||
delegate_to: localhost | ||
|
||
- name: unarchive-gz | ||
unarchive: | ||
src: "{{ file }}.tar.gz" | ||
dest: "dummy" | ||
|
||
- name: unarchive-tar | ||
unarchive: | ||
src: "{{ file }}.tar" | ||
dest: "dummy" | ||
|
||
- name: unarchive-xz | ||
unarchive: | ||
src: "{{ file }}.tar.xz" | ||
dest: "dummy" | ||
|
||
- name: unarchive-zip | ||
unarchive: | ||
src: "{{ file }}.zip" | ||
dest: dummy | ||
extra_opts: | ||
- '-X' | ||
|
||
- name: unarchive-zip-same-owner | ||
unarchive: | ||
src: "{{ file }}.zip" | ||
dest: dummy | ||
extra_opts: | ||
- '-X' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
- name: syncronize-delegate | ||
synchronize: | ||
src: dummy | ||
dest: dummy | ||
delegate_to: localhost | ||
|
||
- name: synchronize-no-same-owner | ||
synchronize: | ||
src: dummy | ||
dest: dummy | ||
owner: false | ||
group: false | ||
|
||
- name: unarchive-no-same-owner | ||
unarchive: | ||
src: "{{ file }}.tar.gz" | ||
dest: dummy | ||
extra_opts: | ||
- '--no-same-owner' | ||
|
||
- name: unarchive-remote-src | ||
unarchive: | ||
src: "{{ file }}.tar.gz" | ||
dest: dummy | ||
extra_opts: | ||
- '--no-same-owner' | ||
|
||
- name: unarchive-unknown-file-ending | ||
unarchive: | ||
src: "{{ file }}" | ||
dest: "dummy" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -56,6 +56,7 @@ | |
offline=False, | ||
project_dir=None, | ||
extra_vars=None, | ||
enable_list=[], | ||
skip_action_validation=True, | ||
) | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,130 @@ | ||
"""Optional rule for avoiding keeping owner/group when transferring files.""" | ||
import re | ||
import sys | ||
from typing import Any, List | ||
|
||
from ansiblelint.errors import MatchError | ||
from ansiblelint.file_utils import Lintable | ||
from ansiblelint.rules import AnsibleLintRule | ||
|
||
|
||
class NoSameOwnerRule(AnsibleLintRule): | ||
|
||
id = 'no-same-owner' | ||
shortdesc = 'Owner should not be kept between different hosts' | ||
description = """ | ||
Optional rule that hihglight dangers of assuming that user/group on the remote | ||
machines may not exist on ansible controller or vice versa. Owner and group | ||
should not be preserved when transferring files between them. | ||
This rule is not enabled by default and was inspired by Zuul execution policy. | ||
See: | ||
https://zuul-ci.org/docs/zuul-jobs/policy.html\ | ||
#preservation-of-owner-between-executor-and-remote | ||
""" | ||
severity = 'LOW' | ||
tags = ['opt-in'] | ||
|
||
def matchplay(self, file: Lintable, data: Any) -> List[MatchError]: | ||
"""Return matches found for a specific playbook.""" | ||
results: List[MatchError] = [] | ||
if file.kind not in ('tasks', 'handlers', 'playbook'): | ||
return results | ||
|
||
results.extend(self.handle_play(file, data)) | ||
return results | ||
|
||
def handle_play(self, lintable: Lintable, task: Any) -> List[MatchError]: | ||
"""Process a play.""" | ||
results = [] | ||
if 'block' in task: | ||
results.extend(self.handle_playlist(lintable, task['block'])) | ||
else: | ||
results.extend(self.handle_task(lintable, task)) | ||
return results | ||
|
||
def handle_playlist(self, lintable: Lintable, playlist: Any) -> List[MatchError]: | ||
"""Process a playlist.""" | ||
results = [] | ||
for play in playlist: | ||
results.extend(self.handle_play(lintable, play)) | ||
return results | ||
|
||
def handle_task(self, lintable: Lintable, task: Any) -> List[MatchError]: | ||
"""Process a task.""" | ||
results = [] | ||
if 'synchronize' in task: | ||
if self.handle_synchronize(task): | ||
print(task) | ||
results.append( | ||
self.create_matcherror( | ||
filename=lintable, linenumber=task['__line__'] | ||
) | ||
) | ||
elif 'unarchive' in task: | ||
if self.handle_unarchive(task): | ||
results.append( | ||
self.create_matcherror( | ||
filename=lintable, linenumber=task['__line__'] | ||
) | ||
) | ||
|
||
return results | ||
|
||
@staticmethod | ||
def handle_synchronize(task: Any) -> bool: | ||
"""Process a synchronize task.""" | ||
if task.get('delegate_to') is not None: | ||
return False | ||
|
||
synchronize = task['synchronize'] | ||
archive = synchronize.get('archive', True) | ||
|
||
if synchronize.get('owner', archive) or synchronize.get('group', archive): | ||
return True | ||
return False | ||
|
||
@staticmethod | ||
def handle_unarchive(task: Any) -> bool: | ||
"""Process unarchive task.""" | ||
unarchive = task['unarchive'] | ||
delegate_to = task.get('delegate_to') | ||
|
||
if ( | ||
delegate_to == 'localhost' | ||
or delegate_to != 'localhost' | ||
and 'remote_src' not in unarchive | ||
): | ||
if unarchive['src'].endswith('zip'): | ||
if '-X' in unarchive.get('extra_opts', []): | ||
return True | ||
if re.search(r'.*\.tar(\.(gz|bz2|xz))?$', unarchive['src']): | ||
if '--no-same-owner' not in unarchive.get('extra_opts', []): | ||
return True | ||
return False | ||
|
||
|
||
# testing code to be loaded only with pytest or when executed the rule file | ||
if "pytest" in sys.modules: | ||
|
||
import pytest | ||
|
||
from ansiblelint.runner import Runner # pylint: disable=ungrouped-imports | ||
|
||
@pytest.mark.parametrize( | ||
("test_file", "failures"), | ||
( | ||
pytest.param( | ||
'examples/roles/role_for_no_same_owner/tasks/fail.yml', 10, id='fail' | ||
), | ||
pytest.param( | ||
'examples/roles/role_for_no_same_owner/tasks/pass.yml', 0, id='pass' | ||
), | ||
), | ||
) | ||
def test_no_same_owner_rule(default_rules_collection, test_file, failures) -> None: | ||
"""Test rule matches.""" | ||
results = Runner(test_file, rules=default_rules_collection).run() | ||
assert len(results) == failures | ||
for result in results: | ||
assert result.message == NoSameOwnerRule.shortdesc |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters