Repository: ansible/ansible

Issue:
SELinux-related behavior change in copy module
I suspect that @bcoca's atomic-copy changes in caf6bd6 can cause some unintended SELinux-related side effects. I've got a playbook that copies a file to the destination host:

Before:

```
[root@nagios limits.d]# ls -lZ
-rw-r--r--. root root system_u:object_r:etc_t:s0       90-nproc.conf
-rw-r--r--. root root unconfined_u:object_r:etc_t:s0   riak.conf
```

After:

```
[root@nagios ~]# ls -lZ /etc/security/limits.d/
-rw-r--r--. root root system_u:object_r:etc_t:s0       90-nproc.conf
-rw-r--r--. root root unconfined_u:object_r:admin_home_t:s0 riak.conf
```

This particular change actually breaks root login and I suspect it's because of the change in SELinux security context from "etc_t" to "admin_home_t".

I don't really know enough about SELinux to comment more extensively on this. I hope someone else can shed some light.



Discussion:
The new atomic_copy creates a hidden temporary file in the same directory
as the target, to guarantee atomicity of the change (avoid access by other
app in middle of move/write and getting inconsistent data). Not sure how
this is affected by selinux rules.

The copy module had this same logic already, i moved it to a more general
spot as mother modules were starting to use the atomic function
incorrectly, now it does affect more modules (template,cron, sysctl, etc)
but copy should be the same.

I don't know enough about selinux to test/verify this issue any help would
be appreciated.

I think the issue is that the "mv" keeps the context of the original file, where as a "cp" will create the context using the rules for the destination directory.  We are now doing "mv" now rather than creating a duplicate file and removing the old.   As such, atomic movement and SELinux contexts may be at odds.

The temporary workaround may actually be to specify the context in this case, which I would agree would be somewhat non-intuitive as folks would not expect the intermediate file to be created and would not think they needed to do this.

Perhaps there is a call to get the default context on the directory, and we could set it to that if not set on the command line.  I think that _would_ fix the problem.

It's got to exist, if not, there's a CLI command for it, I'd bet.

cc @skvidal @sfromm in case you are interested

I'll look this up, seems it should be simple to add context if selinux is
enabled.

Brian Coca

After the shutil.move() call (which is a copy across filesystems), use selinux_context() in module_common to get the context of the target file and then apply that context to the new file via set_context_if_different().  In fact, the section that sets the context at lines 821 through 823 can be moved to just after shutil.move().

Discussed with @bcoca and I will push a fix to @mpdehaan 


Resolving patch:
diff --git a/lib/ansible/module_common.py b/lib/ansible/module_common.py
--- a/lib/ansible/module_common.py
+++ b/lib/ansible/module_common.py
@@ -820,11 +820,9 @@ def atomic_move(self, src, dest):
                     raise
             if self.selinux_enabled():
                 context = self.selinux_context(dest)
-                self.set_context_if_different(src, context, False)
         else:
             if self.selinux_enabled():
                 context = self.selinux_default_context(dest)
-                self.set_context_if_different(src, context, False)
         # Ensure file is on same partition to make replacement atomic
         dest_dir = os.path.dirname(dest)
         dest_file = os.path.basename(dest)
@@ -839,6 +837,8 @@ def cleanup():
 
         try:
             shutil.move(src, tmp_dest)
+            if self.selinux_enabled():
+                self.set_context_if_different(tmp_dest, context, False)
             os.rename(tmp_dest, dest)
             rc = True
         except (shutil.Error, OSError, IOError), e:


Test patch: