Skip to content

Commit

Permalink
Added MissingFilePermissionsRule
Browse files Browse the repository at this point in the history
Makes missing file permissions a linting error in order to proactively
detect security issues that were fixed via
ansible/ansible#67794 at the expense of
breaking usage that relied on default settings.
  • Loading branch information
ssbarnea committed Aug 12, 2020
1 parent 63aedd6 commit 941ac95
Show file tree
Hide file tree
Showing 5 changed files with 120 additions and 2 deletions.
52 changes: 52 additions & 0 deletions lib/ansiblelint/rules/MissingFilePermissionsRule.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# Copyright (c) 2020 Sorin Sbarnea <sorin.sbarnea@gmail.com>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
from ansiblelint.rules import AnsibleLintRule


class MissingFilePermissionsRule(AnsibleLintRule):
id = "208"
shortdesc = 'File permissions not mentioned'
description = (
"Missing mode parameter can cause unexpected file permissions based "
"on version of Ansible being used. Be explicit, or if you still "
"want the default behavior you can use ``mode: preserve`` to avoid "
"hitting this rule. See "
"https://github.com/ansible/ansible/issues/71200"
)
severity = 'VERY_HIGH'
tags = ['unpredictability']
version_added = 'v4.3.0'

_modules = (
'copy',
'file',
'ini_file',
'lineinfile',
'replace',
'template',
'unarchive',
)

def matchtask(self, file, task):
if task["action"]["__ansible_module__"] not in self._modules:
return False

mode = task['action'].get('mode', None)
return not isinstance(mode, str)
54 changes: 54 additions & 0 deletions test/TestMissingFilePermissionsRule.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# Copyright (c) 2020 Sorin Sbarnea <sorin.sbarnea@gmail.com>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
"""MissingFilePermissionsRule tests."""
import pytest

SUCCESS_TASKS = '''
---
- hosts: hosts
tasks:
- name: permissions not missing
file:
path: foo
mode: preserve
'''

FAIL_TASKS = '''
---
- hosts: hosts
tasks:
- name: permissions missing
file:
path: foo
'''


@pytest.mark.parametrize('rule_runner', ("MissingFilePermissionsRule", ), indirect=['rule_runner'])
def test_success(rule_runner):
"""Validate that mode presence avoids hitting the rule."""
results = rule_runner.run_playbook(SUCCESS_TASKS)
assert len(results) == 0


@pytest.mark.parametrize('rule_runner', ("MissingFilePermissionsRule", ), indirect=['rule_runner'])
def test_fail(rule_runner):
"""Validate that missing mode triggers the rule."""
results = rule_runner.run_playbook(FAIL_TASKS)
assert len(results) == 1
12 changes: 12 additions & 0 deletions test/conftest.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import os
from importlib import import_module

import pytest

Expand All @@ -19,4 +20,15 @@ def default_text_runner(default_rules_collection):
return RunFromText(default_rules_collection)


@pytest.fixture
def rule_runner(request):
"""Return runner for a specific rule class."""
rule_name = request.param
module = import_module(f"ansiblelint.rules.{rule_name}")
collection = RulesCollection()
rule = getattr(module, rule_name)
collection.register(rule())
return RunFromText(collection)


# vim: et:sw=4:syntax=python:ts=4:
2 changes: 1 addition & 1 deletion test/nomatchestest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@
action: debug msg="Hello!"

- name: this should be fine too
action: file state=touch dest=./wherever
action: file state=touch dest=./wherever mode=preserve
2 changes: 1 addition & 1 deletion test/unicode.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@

tasks:
- name: bonjour, ça va?
file: state=touch dest=/tmp/naïve.yml
file: state=touch dest=/tmp/naïve.yml mode=preserve

0 comments on commit 941ac95

Please sign in to comment.