environment: Add --user.

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.

Signed-off-by: Ludovic Courtès <ludo@gnu.org>
This commit is contained in:
Mike Gerwitz 2018-01-25 22:29:32 -05:00 committed by Ludovic Courtès
parent 07ec349229
commit e37944d827
No known key found for this signature in database
GPG key ID: 090B11993D9AEBB5
3 changed files with 138 additions and 29 deletions

View file

@ -7286,10 +7286,11 @@ Attempt to build for @var{system}---e.g., @code{i686-linux}.
@cindex container @cindex container
Run @var{command} within an isolated container. The current working Run @var{command} within an isolated container. The current working
directory outside the container is mapped inside the container. directory outside the container is mapped inside the container.
Additionally, a dummy home directory is created that matches the current Additionally, unless overridden with @code{--user}, a dummy home
user's home directory, and @file{/etc/passwd} is configured accordingly. directory is created that matches the current user's home directory, and
The spawned process runs as the current user outside the container, but @file{/etc/passwd} is configured accordingly. The spawned process runs
has root privileges in the context of the container. as the current user outside the container, but has root privileges in
the context of the container.
@item --network @item --network
@itemx -N @itemx -N
@ -7313,6 +7314,31 @@ example, the @code{fontconfig} package inspects
@code{--link-profile} allows these programs to behave as expected within @code{--link-profile} allows these programs to behave as expected within
the environment. 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}] @item --expose=@var{source}[=@var{target}]
For containers, expose the file system @var{source} from the host system For containers, expose the file system @var{source} from the host system
as the read-only file system @var{target} within the container. If as the read-only file system @var{target} within the container. If

View file

@ -163,6 +163,10 @@ (define (show-help)
(display (G_ " (display (G_ "
-P, --link-profile link environment profile to ~/.guix-profile within -P, --link-profile link environment profile to ~/.guix-profile within
an isolated container")) 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_ " (display (G_ "
--share=SPEC for containers, share writable host file system --share=SPEC for containers, share writable host file system
according to SPEC")) according to SPEC"))
@ -250,6 +254,10 @@ (define %options
(option '(#\P "link-profile") #f #f (option '(#\P "link-profile") #f #f
(lambda (opt name arg result) (lambda (opt name arg result)
(alist-cons 'link-profile? #t 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 (option '("share") #t #f
(lambda (opt name arg result) (lambda (opt name arg result)
(alist-cons 'file-system-mapping (alist-cons 'file-system-mapping
@ -410,43 +418,50 @@ (define (launch-environment/fork command inputs paths pure?)
(pid (match (waitpid pid) (pid (match (waitpid pid)
((_ . status) status))))) ((_ . 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?) profile paths link-profile? network?)
"Run COMMAND within a container that features the software in PROFILE. "Run COMMAND within a container that features the software in PROFILE.
Environment variables are set according to PATHS, a list of native search 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 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. store. When NETWORK?, access to the host system network is permitted.
USER-MAPPINGS, a list of file system mappings, contains the user-specified USER-MAPPINGS, a list of file system mappings, contains the user-specified
host file systems to mount inside the container. LINK-PROFILE? creates a host file systems to mount inside the container. If USER is not #f, each
symbolic link from ~/.guix-profile to the environment profile." 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 (mlet %store-monad ((reqs (inputs->requisites
(list (direct-store-path bash) profile)))) (list (direct-store-path bash) profile))))
(return (return
(let* ((cwd (getcwd)) (let* ((cwd (getcwd))
(passwd (getpwuid (getuid))) (home (getenv "HOME"))
(passwd (mock-passwd (getpwuid (getuid))
user
bash))
(home-dir (passwd:dir passwd)) (home-dir (passwd:dir passwd))
;; Bind-mount all requisite store items, user-specified mappings, ;; Bind-mount all requisite store items, user-specified mappings,
;; /bin/sh, the current working directory, and possibly networking ;; /bin/sh, the current working directory, and possibly networking
;; configuration files within the container. ;; configuration files within the container.
(mappings (mappings
(append user-mappings (override-user-mappings
;; Current working directory. user home
(list (file-system-mapping (append user-mappings
(source cwd) ;; Current working directory.
(target cwd) (list (file-system-mapping
(writable? #t))) (source cwd)
;; When in Rome, do as Nix build.cc does: Automagically (target cwd)
;; map common network configuration files. (writable? #t)))
(if network? ;; When in Rome, do as Nix build.cc does: Automagically
%network-file-mappings ;; map common network configuration files.
'()) (if network?
;; Mappings for the union closure of all inputs. %network-file-mappings
(map (lambda (dir) '())
(file-system-mapping ;; Mappings for the union closure of all inputs.
(source dir) (map (lambda (dir)
(target dir) (file-system-mapping
(writable? #f))) (source dir)
reqs))) (target dir)
(writable? #f)))
reqs))))
(file-systems (append %container-file-systems (file-systems (append %container-file-systems
(map file-system-mapping->bind-mount (map file-system-mapping->bind-mount
mappings)))) mappings))))
@ -467,8 +482,7 @@ (define* (launch-environment/container #:key command bash user-mappings
;; The same variables as in Nix's 'build.cc'. ;; The same variables as in Nix's 'build.cc'.
'("TMPDIR" "TEMPDIR" "TMP" "TEMP")) '("TMPDIR" "TEMPDIR" "TMP" "TEMP"))
;; Create a dummy home directory under the same name as on the ;; Create a dummy home directory.
;; host.
(mkdir-p home-dir) (mkdir-p home-dir)
(setenv "HOME" home-dir) (setenv "HOME" home-dir)
@ -495,7 +509,7 @@ (define* (launch-environment/container #:key command bash user-mappings
;; For convenience, start in the user's current working ;; For convenience, start in the user's current working
;; directory rather than the root directory. ;; directory rather than the root directory.
(chdir cwd) (chdir (override-user-dir user home cwd))
(primitive-exit/status (primitive-exit/status
;; A container's environment is already purified, so no need to ;; A container's environment is already purified, so no need to
@ -505,6 +519,60 @@ (define* (launch-environment/container #:key command bash user-mappings
(delq 'net %namespaces) ; share host network (delq 'net %namespaces) ; share host network
%namespaces))))))) %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) (define (link-environment profile home-dir)
"Create a symbolic link from HOME-DIR/.guix-profile to PROFILE." "Create a symbolic link from HOME-DIR/.guix-profile to PROFILE."
(let ((profile-dir (string-append home-dir "/.guix-profile"))) (let ((profile-dir (string-append home-dir "/.guix-profile")))
@ -592,6 +660,7 @@ (define (guix-environment . args)
(container? (assoc-ref opts 'container?)) (container? (assoc-ref opts 'container?))
(link-prof? (assoc-ref opts 'link-profile?)) (link-prof? (assoc-ref opts 'link-profile?))
(network? (assoc-ref opts 'network?)) (network? (assoc-ref opts 'network?))
(user (assoc-ref opts 'user))
(bootstrap? (assoc-ref opts 'bootstrap?)) (bootstrap? (assoc-ref opts 'bootstrap?))
(system (assoc-ref opts 'system)) (system (assoc-ref opts 'system))
(command (or (assoc-ref opts 'exec) (command (or (assoc-ref opts 'exec)
@ -626,6 +695,8 @@ (define (guix-environment . args)
(when (and (not container?) link-prof?) (when (and (not container?) link-prof?)
(leave (G_ "'--link-profile' cannot be used without '--container'~%"))) (leave (G_ "'--link-profile' cannot be used without '--container'~%")))
(when (and (not container?) user)
(leave (G_ "'--user' cannot be used without '--container'~%")))
(with-store store (with-store store
(set-build-options-from-command-line store opts) (set-build-options-from-command-line store opts)
@ -673,6 +744,7 @@ (define (guix-environment . args)
"/bin/sh")))) "/bin/sh"))))
(launch-environment/container #:command command (launch-environment/container #:command command
#:bash bash-binary #:bash bash-binary
#:user user
#:user-mappings mappings #:user-mappings mappings
#:profile profile #:profile profile
#:paths paths #:paths paths

View file

@ -109,6 +109,17 @@ rm $tmpdir/mounts
-- guile -c "$linktest" -- 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"
# Check the exit code. # Check the exit code.
abnormal_exit_code=" abnormal_exit_code="