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
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
@ -7313,6 +7314,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

View file

@ -163,6 +163,10 @@ (define (show-help)
(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"))
@ -250,6 +254,10 @@ (define %options
(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
@ -410,43 +418,50 @@ (define (launch-environment/fork command inputs paths pure?)
(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))))
@ -467,8 +482,7 @@ (define* (launch-environment/container #:key command bash user-mappings
;; 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)
@ -495,7 +509,7 @@ (define* (launch-environment/container #:key command bash user-mappings
;; 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
@ -505,6 +519,60 @@ (define* (launch-environment/container #:key command bash user-mappings
(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")))
@ -592,6 +660,7 @@ (define (guix-environment . args)
(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)
@ -626,6 +695,8 @@ (define (guix-environment . args)
(when (and (not container?) link-prof?)
(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
(set-build-options-from-command-line store opts)
@ -673,6 +744,7 @@ (define (guix-environment . args)
"/bin/sh"))))
(launch-environment/container #:command command
#:bash bash-binary
#:user user
#:user-mappings mappings
#:profile profile
#:paths paths

View file

@ -109,6 +109,17 @@ rm $tmpdir/mounts
-- 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.
abnormal_exit_code="