Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog/62852.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
added conditional X functionality to linux_acl
31 changes: 18 additions & 13 deletions salt/states/linux_acl.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,8 +131,8 @@ def present(name, acl_type, acl_name="", perms="", recurse=False, force=False):
"""
ret = {"name": name, "result": True, "changes": {}, "comment": ""}

_octal = {"r": 4, "w": 2, "x": 1, "-": 0}
_octal_lookup = {0: "-", 1: "r", 2: "w", 4: "x"}
_octal = {"r": 4, "w": 2, "x": 1, "X": 1, "-": 0}
_octal_lookup = {4: "r", 2: "w", 1: "x", 0: "-"}

if not os.path.exists(name):
ret["comment"] = f"{name} does not exist"
Expand Down Expand Up @@ -174,7 +174,8 @@ def present(name, acl_type, acl_name="", perms="", recurse=False, force=False):
user = None

if user:
octal_sum = sum(_octal.get(i, i) for i in perms)
octal_new = sum(_octal.get(i, i) for i in perms)
conditional_x = bool(perms.endswith("X"))
need_refresh = False
# If recursive check all paths retrieved via acl.getfacl
if recurse:
Expand All @@ -188,11 +189,15 @@ def present(name, acl_type, acl_name="", perms="", recurse=False, force=False):
else:
_current_perms_path = __current_perms[path]
for user_acl in _current_perms_path.get(_acl_type, []):
if (
_search_name in user_acl
and user_acl[_search_name]["octal"] == octal_sum
):
acl_found = True
if _search_name in user_acl:
octal_current = user_acl[_search_name]["octal"]
executable = bool(octal_current % 2 == 1)
if octal_current == octal_new or (
conditional_x
and not executable
and octal_current == (octal_new - 1)
):
acl_found = True
if not acl_found:
need_refresh = True
break
Expand All @@ -208,17 +213,17 @@ def present(name, acl_type, acl_name="", perms="", recurse=False, force=False):
ret["comment"] = "Permissions are in the desired state"
else:
_num = user[_search_name]["octal"]
new_perms = "{}{}{}".format(
_octal_lookup[_num & 1],
_octal_lookup[_num & 2],
old_perms = "{}{}{}".format(
_octal_lookup[_num & 4],
_octal_lookup[_num & 2],
_octal_lookup[_num & 1],
)
changes = {
"new": {"acl_name": acl_name, "acl_type": acl_type, "perms": perms},
"old": {
"acl_name": acl_name,
"acl_type": acl_type,
"perms": new_perms,
"perms": old_perms,
},
}

Expand All @@ -227,7 +232,7 @@ def present(name, acl_type, acl_name="", perms="", recurse=False, force=False):
{
"comment": (
"Updated permissions will be applied for "
"{}: {} -> {}".format(acl_name, new_perms, perms)
"{}: {} -> {}".format(acl_name, old_perms, perms)
),
"result": None,
"changes": changes,
Expand Down
257 changes: 251 additions & 6 deletions tests/pytests/unit/states/test_linux_acl.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ def test_present():
# Update - test=False
with patch.dict(linux_acl.__salt__, {"acl.modfacl": mock_modfacl}):
with patch.dict(linux_acl.__opts__, {"test": False}):
comt = "Updated permissions for {}".format(acl_name)
comt = f"Updated permissions for {acl_name}"
ret = {
"name": name,
"comment": comt,
Expand All @@ -118,7 +118,7 @@ def test_present():
{"acl.modfacl": MagicMock(side_effect=CommandExecutionError("Custom err"))},
):
with patch.dict(linux_acl.__opts__, {"test": False}):
comt = "Error updating permissions for {}: Custom err".format(acl_name)
comt = f"Error updating permissions for {acl_name}: Custom err"
ret = {
"name": name,
"comment": comt,
Expand Down Expand Up @@ -148,7 +148,7 @@ def test_present():
# New - test=False
with patch.dict(linux_acl.__salt__, {"acl.modfacl": mock_modfacl}):
with patch.dict(linux_acl.__opts__, {"test": False}):
comt = "Applied new permissions for {}".format(acl_name)
comt = f"Applied new permissions for {acl_name}"
ret = {
"name": name,
"comment": comt,
Expand All @@ -168,7 +168,252 @@ def test_present():
{"acl.modfacl": MagicMock(side_effect=CommandExecutionError("Custom err"))},
):
with patch.dict(linux_acl.__opts__, {"test": False}):
comt = "Error updating permissions for {}: Custom err".format(acl_name)
comt = f"Error updating permissions for {acl_name}: Custom err"
ret = {
"name": name,
"comment": comt,
"changes": {},
"result": False,
}
assert linux_acl.present(name, acl_type, acl_name, perms) == ret

# New - recurse true
with patch.dict(linux_acl.__salt__, {"acl.getfacl": mock}):
# Update - test=True
with patch.dict(linux_acl.__opts__, {"test": True}):
comt = "Updated permissions will be applied for {}: rwx -> {}".format(
acl_name, perms
)
ret = {
"name": name,
"comment": comt,
"changes": {
"new": {
"acl_name": acl_name,
"acl_type": acl_type,
"perms": perms,
},
"old": {
"acl_name": acl_name,
"acl_type": acl_type,
"perms": "rwx",
},
},
"result": None,
}

assert (
linux_acl.present(name, acl_type, acl_name, perms, recurse=True)
== ret
)

# New - recurse true - nothing to do
with patch.dict(linux_acl.__salt__, {"acl.getfacl": mock}):
# Update - test=True
with patch.dict(linux_acl.__opts__, {"test": True}):
comt = "Permissions are in the desired state"
ret = {"name": name, "comment": comt, "changes": {}, "result": True}

assert (
linux_acl.present(name, acl_type, acl_name, perms, recurse=True)
== ret
)

# No acl type
comt = "ACL Type does not exist"
ret = {"name": name, "comment": comt, "result": False, "changes": {}}
assert linux_acl.present(name, acl_type, acl_name, perms) == ret

# default recurse false - nothing to do
with patch.dict(linux_acl.__salt__, {"acl.getfacl": mock}):
# Update - test=True
with patch.dict(linux_acl.__opts__, {"test": True}):
comt = "Permissions are in the desired state"
ret = {"name": name, "comment": comt, "changes": {}, "result": True}

assert (
linux_acl.present(
name, "d:" + acl_type, acl_name, perms, recurse=False
)
== ret
)

# default recurse false - nothing to do
with patch.dict(linux_acl.__salt__, {"acl.getfacl": mock}):
# Update - test=True
with patch.dict(linux_acl.__opts__, {"test": True}):
comt = "Permissions are in the desired state"
ret = {"name": name, "comment": comt, "changes": {}, "result": True}

assert (
linux_acl.present(
name, "d:" + acl_type, acl_name, perms, recurse=False
)
== ret
)

# default recurse true - nothing to do
with patch.dict(linux_acl.__salt__, {"acl.getfacl": mock}):
# Update - test=True
with patch.dict(linux_acl.__opts__, {"test": True}):
comt = "Permissions are in the desired state"
ret = {"name": name, "comment": comt, "changes": {}, "result": True}

assert (
linux_acl.present(
name, "d:" + acl_type, acl_name, perms, recurse=True
)
== ret
)


def test_present_conditional_x():
"""
Test to ensure a Linux ACL containing a conditional X is applied correctly
"""
maxDiff = None
name = "/root"
acl_type = "users"
acl_name = "damian"
perms = "rwX"

mock = MagicMock(
side_effect=[
{name: {acl_type: [{acl_name: {"octal": 5}}]}},
{name: {acl_type: [{acl_name: {"octal": 5}}]}},
{name: {acl_type: [{acl_name: {"octal": 5}}]}},
{name: {acl_type: [{}]}},
{name: {acl_type: [{}]}},
{name: {acl_type: [{}]}},
{
name: {acl_type: [{acl_name: {"octal": 7}}]},
name + "/foo": {acl_type: [{acl_name: {"octal": 5}}]},
},
{
name: {acl_type: [{acl_name: {"octal": 7}}]},
name + "/foo": {acl_type: [{acl_name: {"octal": 7}}]},
},
{name: {acl_type: ""}},
{
name: {"defaults": {"users": [{acl_name: {"octal": 7}}]}},
name + "/foo": {"defaults": {"users": [{acl_name: {"octal": 7}}]}},
},
{
name: {"defaults": {"users": [{acl_name: {"octal": 7}}]}},
name + "/foo": {"defaults": {"users": [{acl_name: {"octal": 7}}]}},
},
{
name: {"defaults": {"users": [{acl_name: {"octal": 7}}]}},
name + "/foo": {"defaults": {"users": [{acl_name: {"octal": 7}}]}},
},
]
)
mock_modfacl = MagicMock(return_value=True)

with patch.dict(linux_acl.__salt__, {"acl.getfacl": mock}):
# Update - test=True
with patch.dict(linux_acl.__opts__, {"test": True}):
comt = "Updated permissions will be applied for {}: r-x -> {}".format(
acl_name, perms
)
ret = {
"name": name,
"comment": comt,
"changes": {
"new": {
"acl_name": acl_name,
"acl_type": acl_type,
"perms": perms,
},
"old": {
"acl_name": acl_name,
"acl_type": acl_type,
"perms": "r-x",
},
},
"result": None,
}

assert linux_acl.present(name, acl_type, acl_name, perms) == ret
# Update - test=False
with patch.dict(linux_acl.__salt__, {"acl.modfacl": mock_modfacl}):
with patch.dict(linux_acl.__opts__, {"test": False}):
comt = f"Updated permissions for {acl_name}"
ret = {
"name": name,
"comment": comt,
"changes": {
"new": {
"acl_name": acl_name,
"acl_type": acl_type,
"perms": perms,
},
"old": {
"acl_name": acl_name,
"acl_type": acl_type,
"perms": "r-x",
},
},
"result": True,
}
assert linux_acl.present(name, acl_type, acl_name, perms) == ret
# Update - modfacl error
with patch.dict(
linux_acl.__salt__,
{"acl.modfacl": MagicMock(side_effect=CommandExecutionError("Custom err"))},
):
with patch.dict(linux_acl.__opts__, {"test": False}):
comt = f"Error updating permissions for {acl_name}: Custom err"
ret = {
"name": name,
"comment": comt,
"changes": {},
"result": False,
}
assert linux_acl.present(name, acl_type, acl_name, perms) == ret
# New - test=True
with patch.dict(linux_acl.__salt__, {"acl.modfacl": mock_modfacl}):
with patch.dict(linux_acl.__opts__, {"test": True}):
comt = "New permissions will be applied for {}: {}".format(
acl_name, perms
)
ret = {
"name": name,
"comment": comt,
"changes": {
"new": {
"acl_name": acl_name,
"acl_type": acl_type,
"perms": perms,
}
},
"result": None,
}
assert linux_acl.present(name, acl_type, acl_name, perms) == ret
# New - test=False
with patch.dict(linux_acl.__salt__, {"acl.modfacl": mock_modfacl}):
with patch.dict(linux_acl.__opts__, {"test": False}):
comt = f"Applied new permissions for {acl_name}"
ret = {
"name": name,
"comment": comt,
"changes": {
"new": {
"acl_name": acl_name,
"acl_type": acl_type,
"perms": perms,
}
},
"result": True,
}
assert linux_acl.present(name, acl_type, acl_name, perms) == ret
# New - modfacl error
with patch.dict(
linux_acl.__salt__,
{"acl.modfacl": MagicMock(side_effect=CommandExecutionError("Custom err"))},
):
with patch.dict(linux_acl.__opts__, {"test": False}):
comt = f"Error updating permissions for {acl_name}: Custom err"
ret = {
"name": name,
"comment": comt,
Expand Down Expand Up @@ -395,7 +640,7 @@ def test_list_present():
{"acl.modfacl": MagicMock(side_effect=CommandExecutionError("Custom err"))},
):
with patch.dict(linux_acl.__opts__, {"test": False}):
comt = "Error updating permissions for {}: Custom err".format(acl_names)
comt = f"Error updating permissions for {acl_names}: Custom err"
expected = {
"name": name,
"comment": comt,
Expand Down Expand Up @@ -453,7 +698,7 @@ def test_list_present():
{"acl.modfacl": MagicMock(side_effect=CommandExecutionError("Custom err"))},
):
with patch.dict(linux_acl.__opts__, {"test": False}):
comt = "Error updating permissions for {}: Custom err".format(acl_names)
comt = f"Error updating permissions for {acl_names}: Custom err"
expected = {
"name": name,
"comment": comt,
Expand Down