(address . guix-patches@gnu.org)
This change allows overriding the home directory of all filesystem mappings to
help hide the identity of the calling user in a container.
* doc/guix.texi (Invoking guix environment)[--container]: Mention --user.
[--user]: Add item.
* guix/scripts/environment.scm (show-help): Add --user.
(%options): Add --user.
(launch-environment/container) Add 'user' parameter. Update doc. Override
'user-mappings' using 'override-user-mappings'. Consider override for chdir.
(mock-passwd, user-override-home, overrid-euser-dir): New procedures.
(guix-environment): Disallow --user without --container. Provide user to
'launch-environment/container'.
* tests/guix-environment.sh: Add user test.
---
doc/guix.texi | 34 ++++++++++--
guix/scripts/environment.scm | 122 ++++++++++++++++++++++++++++++++++---------
tests/guix-environment.sh | 10 ++++
3 files changed, 137 insertions(+), 29 deletions(-)
Toggle diff (279 lines)
diff --git a/doc/guix.texi b/doc/guix.texi
index 3b6ae1ab9..8218c6637 100644
--- a/doc/guix.texi
+++ b/doc/guix.texi
@@ -7156,10 +7156,11 @@ Attempt to build for @var{system}---e.g., @code{i686-linux}.
@cindex container
Run @var{command} within an isolated container. The current working
directory outside the container is mapped inside the container.
-Additionally, a dummy home directory is created that matches the current
-user's home directory, and @file{/etc/passwd} is configured accordingly.
-The spawned process runs as the current user outside the container, but
-has root privileges in the context of the container.
+Additionally, unless overridden with @code{--user}, a dummy home
+directory is created that matches the current user's home directory, and
+@file{/etc/passwd} is configured accordingly. The spawned process runs
+as the current user outside the container, but has root privileges in
+the context of the container.
@item --network
@itemx -N
@@ -7183,6 +7184,31 @@ example, the @code{fontconfig} package inspects
@code{--link-profile} allows these programs to behave as expected within
the environment.
+@item --user=@var{user}
+@itemx -u @var{user}
+For containers, use the username @var{user} in place of the current
+user. The generated @file{/etc/passwd} entry within the container will
+contain the name @var{user}; the home directory will be
+@file{/home/USER}; and no user GECOS data will be copied. @var{user}
+need not exist on the system.
+
+Additionally, any shared or exposed path (see @code{--share} and
+@code{--expose} respectively) whose target is within the current user's
+home directory will be remapped relative to @file{/home/USER}; this
+includes the automatic mapping of the current working directory.
+
+@example
+# will expose paths as /home/foo/wd, /home/foo/test, and /home/foo/target
+cd $HOME/wd
+guix environment --container --user=foo \
+ --expose=$HOME/test \
+ --expose=/tmp/target=$HOME/target
+@end example
+
+While this will limit the leaking of user identity through home paths
+and each of the user fields, this is only one useful component of a
+broader privacy/anonymity solution---not one in and of itself.
+
@item --expose=@var{source}[=@var{target}]
For containers, expose the file system @var{source} from the host system
as the read-only file system @var{target} within the container. If
diff --git a/guix/scripts/environment.scm b/guix/scripts/environment.scm
index 771574c15..f50018faf 100644
--- a/guix/scripts/environment.scm
+++ b/guix/scripts/environment.scm
@@ -161,6 +161,10 @@ COMMAND or an interactive shell in that environment.\n"))
(display (G_ "
-P, --link-profile link environment profile to ~/.guix-profile within
an isolated container"))
+ (display (G_ "
+ -u, --user=USER instead of copying the name and home of the current
+ user into an isolated container, use the name USER
+ with home directory /home/USER"))
(display (G_ "
--share=SPEC for containers, share writable host file system
according to SPEC"))
@@ -243,6 +247,10 @@ COMMAND or an interactive shell in that environment.\n"))
(option '(#\P "link-profile") #f #f
(lambda (opt name arg result)
(alist-cons 'link-profile? #t result)))
+ (option '(#\u "user") #t #f
+ (lambda (opt name arg result)
+ (alist-cons 'user arg
+ (alist-delete 'user result eq?))))
(option '("share") #t #f
(lambda (opt name arg result)
(alist-cons 'file-system-mapping
@@ -390,43 +398,50 @@ environment variables are cleared before setting the new ones."
(pid (match (waitpid pid)
((_ . status) status)))))
-(define* (launch-environment/container #:key command bash user-mappings
+(define* (launch-environment/container #:key command bash user user-mappings
profile paths link-profile? network?)
"Run COMMAND within a container that features the software in PROFILE.
Environment variables are set according to PATHS, a list of native search
paths. The global shell is BASH, a file name for a GNU Bash binary in the
store. When NETWORK?, access to the host system network is permitted.
USER-MAPPINGS, a list of file system mappings, contains the user-specified
-host file systems to mount inside the container. LINK-PROFILE? creates a
-symbolic link from ~/.guix-profile to the environment profile."
+host file systems to mount inside the container. If USER is not #f, each
+target of USER-MAPPINGS will be re-written relative to '/home/USER', and USER
+will be used for the passwd entry. LINK-PROFILE? creates a symbolic link from
+~/.guix-profile to the environment profile."
(mlet %store-monad ((reqs (inputs->requisites
(list (direct-store-path bash) profile))))
(return
(let* ((cwd (getcwd))
- (passwd (getpwuid (getuid)))
+ (home (getenv "HOME"))
+ (passwd (mock-passwd (getpwuid (getuid))
+ user
+ bash))
(home-dir (passwd:dir passwd))
;; Bind-mount all requisite store items, user-specified mappings,
;; /bin/sh, the current working directory, and possibly networking
;; configuration files within the container.
(mappings
- (append user-mappings
- ;; Current working directory.
- (list (file-system-mapping
- (source cwd)
- (target cwd)
- (writable? #t)))
- ;; When in Rome, do as Nix build.cc does: Automagically
- ;; map common network configuration files.
- (if network?
- %network-file-mappings
- '())
- ;; Mappings for the union closure of all inputs.
- (map (lambda (dir)
- (file-system-mapping
- (source dir)
- (target dir)
- (writable? #f)))
- reqs)))
+ (override-user-mappings
+ user home
+ (append user-mappings
+ ;; Current working directory.
+ (list (file-system-mapping
+ (source cwd)
+ (target cwd)
+ (writable? #t)))
+ ;; When in Rome, do as Nix build.cc does: Automagically
+ ;; map common network configuration files.
+ (if network?
+ %network-file-mappings
+ '())
+ ;; Mappings for the union closure of all inputs.
+ (map (lambda (dir)
+ (file-system-mapping
+ (source dir)
+ (target dir)
+ (writable? #f)))
+ reqs))))
(file-systems (append %container-file-systems
(map file-system-mapping->bind-mount
mappings))))
@@ -447,8 +462,7 @@ symbolic link from ~/.guix-profile to the environment profile."
;; The same variables as in Nix's 'build.cc'.
'("TMPDIR" "TEMPDIR" "TMP" "TEMP"))
- ;; Create a dummy home directory under the same name as on the
- ;; host.
+ ;; Create a dummy home directory.
(mkdir-p home-dir)
(setenv "HOME" home-dir)
@@ -475,7 +489,7 @@ symbolic link from ~/.guix-profile to the environment profile."
;; For convenience, start in the user's current working
;; directory rather than the root directory.
- (chdir cwd)
+ (chdir (override-user-dir user home cwd))
(primitive-exit/status
;; A container's environment is already purified, so no need to
@@ -485,6 +499,60 @@ symbolic link from ~/.guix-profile to the environment profile."
(delq 'net %namespaces) ; share host network
%namespaces)))))))
+(define (mock-passwd passwd user-override shell)
+ "Generate mock information for '/etc/passwd'. If USER-OVERRIDE is not '#f',
+it is expected to be a string representing the mock username; it will produce
+a user of that name, with a home directory of '/home/USER-OVERRIDE', and no
+GECOS field. If USER-OVERRIDE is '#f', data will be inherited from PASSWD.
+In either case, the shadow password and UID/GID are cleared, since the user
+runs as root within the container. SHELL will always be used in place of the
+shell in PASSWD.
+
+The resulting vector is suitable for use with Guile's POSIX user procedures.
+
+See passwd(5) for more information each of the fields."
+ (if user-override
+ (vector
+ user-override
+ "x" "0" "0" ;; no shadow, user is now root
+ "" ;; no personal information
+ (user-override-home user-override)
+ shell)
+ (vector
+ (passwd:name passwd)
+ "x" "0" "0" ;; no shadow, user is now root
+ (passwd:gecos passwd)
+ (passwd:dir passwd)
+ shell)))
+
+(define (user-override-home user)
+ "Return home directory for override user USER."
+ (string-append "/home/" user))
+
+(define (override-user-mappings user home mappings)
+ "If a username USER is provided, rewrite each HOME prefix in file system
+mappings MAPPINGS to a home directory determined by 'override-user-dir';
+otherwise, return MAPPINGS."
+ (if (not user)
+ mappings
+ (map (lambda (mapping)
+ (let ((target (file-system-mapping-target mapping)))
+ (if (string-prefix? home target)
+ (file-system-mapping
+ (source (file-system-mapping-source mapping))
+ (target (override-user-dir user home target))
+ (writable? (file-system-mapping-writable? mapping)))
+ mapping)))
+ mappings)))
+
+(define (override-user-dir user home dir)
+ "If username USER is provided, overwrite string prefix HOME in DIR with a
+directory determined by 'user-override-home'; otherwise, return DIR."
+ (if (and user (string-prefix? home dir))
+ (string-append (user-override-home user)
+ (substring dir (string-length home)))
+ dir))
+
(define (link-environment profile home-dir)
"Create a symbolic link from HOME-DIR/.guix-profile to PROFILE."
(let ((profile-dir (string-append home-dir "/.guix-profile")))
@@ -572,6 +640,7 @@ message if any test fails."
(container? (assoc-ref opts 'container?))
(link-prof? (assoc-ref opts 'link-profile?))
(network? (assoc-ref opts 'network?))
+ (user (assoc-ref opts 'user))
(bootstrap? (assoc-ref opts 'bootstrap?))
(system (assoc-ref opts 'system))
(command (or (assoc-ref opts 'exec)
@@ -606,6 +675,8 @@ message if any test fails."
(when (and (not container?) link-prof?)
(leave (G_ "--link-prof cannot be used without --container~%")))
+ (when (and (not container?) user)
+ (leave (G_ "--user cannot be used without --container~%")))
(with-store store
(set-build-options-from-command-line store opts)
@@ -653,6 +724,7 @@ message if any test fails."
"/bin/sh"))))
(launch-environment/container #:command command
#:bash bash-binary
+ #:user user
#:user-mappings mappings
#:profile profile
#:paths paths
diff --git a/tests/guix-environment.sh b/tests/guix-environment.sh
index e995636df..a1ce96579 100644
--- a/tests/guix-environment.sh
+++ b/tests/guix-environment.sh
@@ -74,6 +74,16 @@ guix environment --bootstrap --ad-hoc guile-bootstrap --pure \
-- guile -c "$linktest"
)
+# Test that user can be mocked.
+usertest='(exit (and (string=? (getenv "HOME") "/home/foognu")
+ (string=? (passwd:name (getpwuid 0)) "foognu")
+ (file-exists? "/home/foognu/umock")))'
+touch "$tmpdir/umock"
+HOME="$tmpdir" guix environment --bootstrap --container --user=foognu \
+ --ad-hoc guile-bootstrap --pure \
+ --share="$tmpdir/umock" \
+ -- guile -c "$usertest"
+
# Make sure '-r' works as expected.
rm -f "$gcroot"
expected="`guix environment --bootstrap --ad-hoc guile-bootstrap \
--
2.15.1
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v2
iQIcBAEBCgAGBQJaaqCcAAoJEIyRe39dxRuiCZcP/0VEvCAiwTsb96+j8KGKZeDS
3pErm7SbPR5pNkipDzyp1qqyGTreWXSnLBy3N9ChXV+j35dHdZ3VhOVS6OUJMiJv
CxdG7y86naxbSidQzxMmX0eujUxOvL2xmhemVuiOuKmbHana1eI2NG216pneNaes
zwngRmALoB2M9nvNjadKgtWtyMasOJpy6ZfeW9Zze4W521WJnn8KOqAKzJbqraCN
qmAxkg80DoVLKlIW6VHetcYmN3WEw+XqV1vw8PFofiKwkIjBZlCh28AK7h5eweFi
MGXFbFTifwQg2asLDT4vpBek+DcE+4PyLid5hZMVsiAKEbLP9dHRJCbNUcSeCLQs
NWxkks/hMEg1QddReuLzgUsOhyGW2RpVWRhh7+L+YIkL18yRfW63viTdbO2+ucfa
yJGiUrmlgAk64lYiV0oDSgq36CgBlhdeUVchLgBEv9AgqMBUZtF4sc6AfnilASHM
8Flv/pcBhz0JJ0M/8YjrzlUCz6b7KnkAKycxOQgvpyq8Qu9VoOE5XN7c1qEJ4ypE
LIKbiMdoH77ySqyd8B4X/fXiqyIGaUq7Umv1ii/g4PImeqxWjrwgAVkFMAwZFIRK
CKASx4p+1+zQKONs93cYyHoTzxaNEC9TJCxnRD6Lioqos8wuGVBV0Pi7G/OFSmu7
xktWhFqAE9HRI/SJZIDs
=OLI8
-----END PGP SIGNATURE-----