Difference between pages "Talk:User and Group Management" and "SFTP Only Access"

From Funtoo
(Difference between pages)
Jump to: navigation, search
 
 
Line 1: Line 1:
{{fancynote|the Discussion page contains a much more ambitious proposal that I decided was too complex to tackle all at once. It is being split into bite-sized portions (Phases) on the main page.}}
+
= Context =
  
== User and Group Dependencies ==
+
In some cases, it can be useful to set up an access on your Funtoo box such as a user:
 +
* does not see the whole contents of the machine but, instead, remains "jailed" in a home directory
 +
* is able to transfer files back and forth on the box via SFTP
 +
* does not have access to a shell
  
[http://www.exherbo.org/docs/exheres-for-smarties.html#repository_metadata Exheres] defines a dependency-based mechanism for ebuilds to specify their user and group dependencies, which is an appropriate mechanism for specifying dependencies. The specific syntax used is <tt>user/foo</tt> to specify a dependency on user <tt>foo</tt>, and <tt>group/bar</tt> to specify a dependency on group <tt>bar</tt> existing. Dependencies can be build-time or run-time, as required.
+
Such a SFTP only access is easy to setup:
  
Using dependencies for this purpose allows Portage to create these users and groups at exactly the right time -- prior to build or prior to install, as necessary, and will work just fine with binary packages, with some potential caveats (noted later in the document.) It also allows user and group creation to be affected by <tt>USE</tt> variable settings.
+
# Assign a group (e.g. ''sftponly'') to users that must be restricted to a SFTP-only account
 +
# Change a bit the configuration of OpenSSH so that users belonging to your sftp-only group are given a chrooted access
 +
# Make OpenSSH ignore any other command than running sftp-server on the server side for users belonging to your sftp-only group (this is where the trick lies !)
  
This document suggests following the Exheres syntax:
+
= Quick start =
 +
 
 +
First, a dedicated group must be created. For the sake of the example we use sftponly here, use whatever name fits your preferences:
  
 
<pre>
 
<pre>
DEPEND="user/lighttpd group/web-server"
+
# groupadd sftponly
RDEPEND="user/lighttpd group/web-server"
+
 
</pre>
 
</pre>
  
All this tells Portage is that "This ebuild needs a <tt>lighttpd</tt> user and <tt>web-server</tt> group." But it does not tell Portage what UID it should be, nor does it provide other necessary settings for the user. This data is defined within the Portage tree, and the mechanism for defining this data is described below.
+
Next in the configuration of OpenSSH (located in '''/etc/sshd/sshd_config''') locate:
  
== Profile Settings ==
+
<pre>
 +
Subsystem      sftp    /usr/lib64/misc/sftp-server
 +
</pre>
  
The user or group dependency will just tell Portage that this particular package requires a particular user or group, but any detailed information related to this user or group, such as suggested UID/GID, shell, etc, is stored in the Portage tree itself, and specifically in the Portage ''profile''. The mechanism for defining this information is described below:
+
and change it for:
 
+
=== Core Portage Trees ===
+
 
+
For "core" Portage trees (not overlays,) specific user and group settings are defined using Portage's ''cascading profile'' functionality. Portage would be enhanced to recognize <tt>accounts/users</tt> and <tt>accounts/groups</tt> directories inside profile directories. Users and groups would be defined in these directories, with one user or group per file, and the filename specifying the name of the user or group. Cascading functionality would be enabled so that the full set of user and group data could be a collection of all users and groups defined in parent profiles. This would provide a handy mechanism to share user and group definitions across different operating systems, while allowing for local variations when needed. It makes sense to leverage cascading profiles as much as possible.
+
 
+
=== Overlays ===
+
 
+
The approach described above does not work for overlays -- how are they to extend user and group settings automatically, as required by the ebuilds contained in the overlay?
+
 
+
The proposed solution is to allow overlays to add users and groups via the <tt>OVERLAY_DIR/profiles/accounts/groups</tt> and <tt>PORTDIR/profiles/accounts/users</tt> directories. These directories will ''always'' be searched for user and group data for all active overlays, and merged into the set defined by the profiles. This provides an automatic mechanism for overlays to inject user and group data that they require, without requiring any manual configuration on behalf of the Gentoo/Funtoo Linux user.
+
 
+
This way, Portage can have elegant overlay support inherent in the Exheres "global repository of user/group data" design, while still having an extensible mechanism to define users and groups using cascading profiles. In my opinion, this is the best of both worlds.
+
 
+
=== Account Resolution ===
+
 
+
See the following pseudo-code for how resolution of cascading profiles and overlays should work together to resolve user settings. One important thing to note is that user and group resolution cascades through the profiles to create a master list of users, groups and defaults. This master list is extended by any overlays that are active. Then, when user or group data is requested, the resolved user, group and defaults lists are used to generate the resultant data.
+
 
+
'''Users pseudo-code, with Groups being implemented identically:'''
+
  
 
<pre>
 
<pre>
class Profile:
+
Subsystem      sftp    internal-sftp
 
+
  def __init__(self,path):
+
    self.path = path
+
    self._processed_user_defaults = False
+
    self._required_user_fields = []
+
    self._alternate_user_fields = {}
+
    self.parents = []
+
    # sample code to recursively create Parent profiles:
+
    if os.path.exists("%s/parents" % self.path):
+
      a=open("%s/parents" % self.path,"r")
+
      for line in a:
+
        self.parents.append(Profile(self.resolve_path(line)))
+
      a.close()
+
 
+
  @property
+
  def users(self):
+
    """ returns a dictionary mapping user names to the files on disk defining each user (cascading) """
+
    users = {}
+
    for parent in self.parents:
+
      users.update(self.parent.users)
+
    for userfile in glob.glob("accounts/users/*"):
+
      users[os.path.dirname(userfile)] = os.path.abspath(userfile)
+
    for overlay in self.overlays:
+
      users.update(self.overlay.users)
+
    return users
+
 
+
  def userData(self,user):
+
    """ returns a dictionary of key/value pairs defining the variables for specified user. Note:
+
        * alternative key names are mapped to primary key names
+
        * an exception is thrown if required fields are missing
+
    """
+
    out = {}
+
    if user in self.users:
+
      user_data = grabFile(self.users[user])
+
      out = grabFile(self.defaults["user"])
+
      required = []
+
      alternatives = {}
+
      if "required" in out:
+
        for req_key in out["required"].split(','):
+
          alts = req_key.split('|')
+
          required.append(alts[0])
+
          if len(alts) > 1:
+
          for alt_key in alts[1:]:
+
            alternatives[alt_key] = alts[0] 
+
      if "parent" in user_data:
+
        # note, this next line requires a grabFile() implementation that supports alternatives, and
+
        # will use this dict to map any alternative names to the primary name in the return data:
+
        out.update(grabFile(self.defaults[user_data["parent"]],alternatives=alternatives))
+
        out.update(user_data,alternatives=alternatives)
+
    for req_key in required:
+
      if not req_key in out:
+
        raise RequiredKeyError(user,req_key)
+
    return out
+
 
+
  @property
+
  def defaults(self):
+
    """ returns a dictionary mapping defaults names to the files on disk defining each default (cascading) """
+
    defaults = {}
+
    for parent in self.parents:
+
      defaults.update(self.parent.defaults)
+
    for defaultsfile in glob.glob("accounts/defaults/*"):
+
      defaults[os.path.dirname(defaultsfile)] = os.path.abspath(defaultsfile)
+
    for overlay in self.overlays:
+
      defaults.update(overlay.user_defaults)
+
    return defaults
+
 
+
profile = Profile("/etc/make.profile")
+
my_user = profile.userData("nginx")
+
print my_user["desc"]
+
 
+
 
</pre>
 
</pre>
  
=== User and Group Data Format ===
+
Now the $100 question: ''"how can OpenSSH can be told to restrict a user access to a simple sftp session?"'' Simple! Assuming that ''sftponly'' is the group you use for for your restricted users, just add to the file '''/etc/sshd/sshd_config''' the following statement:
 
+
==== Users ====
+
 
+
In a given profile directory, <tt>accounts/users/'''myuser'''</tt> will define settings for a user with the name of <tt>myuser</tt>. The file format used to define users is very similar to and compatible with Exheres, using standard <tt>make.conf</tt>-style key=value syntax, with quoting required for values with whitespace. The following field names are suggested to be used for the initial users implementation. Note that this file format is extensible -- Portage must not complain about any additional fields in the users, groups or defaults files that are not specified above. This allows these formats to be easily extended for alternate operating systems or other distributions without requiring patches to Portage.
+
 
+
{| {{table}}
+
! Name
+
! Alternate Name
+
! Description
+
! Example
+
! Notes
+
|-
+
|<tt>shell</tt>
+
|N/A
+
|login shell
+
|<tt>/bin/bash</tt>
+
|
+
|-
+
|<tt>home</tt>
+
|N/A
+
|home directory
+
|<tt>/dev/null</tt>
+
|
+
|-
+
|<tt>group</tt>
+
|<tt>primary_group</tt>
+
|primary group
+
|<tt>wheel</tt>
+
|
+
|-
+
|<tt>extra_groups</tt>
+
|N/A
+
|other group memberships
+
|<tt>"audio,cdrom"</tt>
+
|''comma-delimited list''
+
|-
+
|<tt>uid</tt>
+
|<tt>preferred_uid</tt>
+
|preferred user ID (not guaranteed)
+
|<tt>37</tt>
+
|Will be bound by <tt>SYS_UID_MIN</tt> and <tt>SYS_UID_MAX</tt> defined in <tt>/etc/login.defs</tt>?
+
|-
+
|<tt>desc</tt>
+
|<tt>gecos</tt>
+
|Description/GECOS field
+
|<tt>"An account for fun"</tt>
+
|
+
|-
+
|<tt>parent</tt>
+
|N/A
+
|parent default file
+
|<tt>user-server</tt>
+
|
+
|}
+
 
+
Example file <tt>accounts/users/foo</tt>:
+
  
 
<pre>
 
<pre>
shell=/bin/bash
+
# Restricted users, no TCP connexions bouncing, no X tunneling.
home=/dev/null
+
Match group sftponly
group=foo
+
        ChrootDirectory /home/%u
extra_groups="foo bar oni"
+
        X11Forwarding no
uid=37
+
        AllowTcpForwarding no
desc="The cool account"
+
        ForceCommand internal-sftp
 
</pre>
 
</pre>
  
==== Groups ====
+
To understand how it works, you must be aware that, when you open an SSH session, the SSHD process launch a process on the server side which could be:
 +
* a shell => ssh login@host
 +
* a kind of dedicated ftp daemon (sftp-server) => sftp user@host
  
* <tt>accounts/groups/'''mygroup'''</tt> will define settings a group with the name of <tt>mygroup</tt>.
+
TBC
  
==== Defaults ====
+
[[Category:HOWTO]]
 
+
The UID/GID management framework supports the ability to explicitly define default values for all users and groups, or a subset of users and groups. In addition, these default values can be overridden by child profiles. This functionality allows default values to be overridden, and also provides a mechanism for profiles to specify which fields are required for that profile. This allows alternate platforms to have different required values, and also allows different Gentoo-based distributions to have different policies regarding required fields. This allows policy to be defined per distribution rather than being hard-coded into Portage itself.
+
 
+
Defaults can be defined inside the <tt>accounts/defaults</tt> directory inside each profile directory. The file <tt>accounts/defaults/user</tt>, if it exists, will be used to define any default settings for user accounts. The file <tt>accounts/defaults/group</tt>, if it exists, will be used to define any default settings for group accounts. These files are typically defined ''in one location'' for an entire set of cascading profiles, such as <tt>profiles/base</tt>.
+
 
+
Defaults files consist of key=value pairs, identical to user and group files. Note that the <tt>parent</tt> keyword is not valid in defaults files. A new keyword <tt>required</tt> specifies the required fields for any child users or groups, and may only be specified in the master defaults file 'user' or 'group':
+
 
+
{| {{table}}
+
! Name
+
! Description
+
! Example
+
! Required
+
! Default
+
! Notes
+
|-
+
|<tt>required</tt>
+
|Required fields
+
|<tt>"shell,home,desc<nowiki>|</nowiki>gecos"</tt>
+
|No
+
|''None''
+
|''comma-delimited list'', with "<tt><nowiki>|</nowiki></tt>" used to specify alternate names
+
|}
+
 
+
==== Alternate Defaults ====
+
 
+
In addition, other files in <tt>defaults</tt> can be created, and these files may be used to specify alternate default settings for users and groups, which can be overridden by child profiles. For example, an <tt>accounts/users/foo</tt> file that contains a <tt>parent=user-server</tt> would use the file <tt>accounts/defaults/user-server</tt> for its inherited default settings. The suggested convention for <tt>defaults</tt> values is to prefix user defaults with "<tt>user-</tt>" and group defaults with "<tt>group-</tt>", but this convention must not be enforced by Portage.
+
 
+
Any defaults files can be overridden by child profiles, which will result in the respective default settings changing for all users and groups that use those defaults.
+
 
+
==== Defaults Parsing Rules ====
+
 
+
Note that all alternate defaults files (such as <tt>user-server</tt>) always inherit (and optionally override) the global defaults defined in <tt>user</tt> and <tt>group</tt>. This means that a <tt>required</tt> setting defined in <tt>user</tt> will be inherited by <tt>user-server</tt> automatically. This allows the <tt>required</tt> field for users to be set globally in <tt>user</tt>, and makes it possible to override it easily, by simply providing a new <tt>user</tt> file in a child profile.
+
 
+
* A default setting defined in <tt>user</tt> or <tt>group</tt> can be ''unset'' by setting it to a value of <tt>""</tt>.
+
* Non-required fields that have not been explicitly defined have a default value of <tt>""</tt> (the empty string).
+
* Required fields that are unset or have a value of <tt>""</tt> should not be allowed and should be flagged as invalid by Portage.
+
 
+
=== User and Group Creation ===
+
 
+
The commands actually used by Portage to create users and groups need to be able to be customizable, as they vary by operating system.
+
 
+
Here are some possible mechanisms to implement this functionality, listed in order of personal preference:
+
 
+
# Add a <tt>plugins</tt> directory to profiles and create <tt>user-add</tt> and <tt>group-add</tt> scripts within these directories. This allows the <tt>user-add</tt> and <tt>group-add</tt> scripts to be different between MacOS X and Linux, for example, while allowing common platforms to re-use existing scripts. Users could override the user-creation behavior by creating <tt>/etc/portage/plugins/user-add</tt> script.
+
# Add <tt>virtual/user-manager</tt> to every system profile which would install <tt>user-add</tt> and <tt>group-add</tt> commands to a Portage plug-in directory. These commands would be used for creating all users and groups on the system, would have a defined command-line API, and could vary based on OS by tweaking the virtual in the system profile.
+
# Add internal logic to Portage for adding groups and users to various operating systems. I think this solution would be sub-optimal as it is less "tweakable". User and group creation is something that can be useful to tweak in various circumstances, especially by power users.
+
 
+
=== Migration ===
+
 
+
What remains to be defined is how to transition from <tt>enewgroup</tt> and <tt>enewuser</tt> that are currently being called from <tt>pkg_setup</tt>. The new implementation should be backwards-compatible with the old system to ease transition.
+
 
+
Options:
+
 
+
# call <tt>pkg_setup</tt> during dependency generation and use <tt>enewgroup</tt> and <tt>enewuser</tt> wrappers to inject dependency info into the metadata, and emit a deprecation warning. Pass only the user/group name to the new system, which would provide its own UID/GID info. This may not be feasible.
+
# brute-force - grep the ebuild for legacy commands during metadata generation. Integrate new-style dependencies into metadata. This is possibly the least elegant solution but may be the simplest approach.
+
# fallback - tweak the legacy commands to call the new framework. This means that older ebuilds would not be able to have their users and groups created at the same time as new-style ebuilds (dependency fulfillment time.) However, this may be the most elegant solution and also the least hackish.
+
 
+
The last option seems best.
+
 
+
=== Architecture ===
+
 
+
Here are the various architectural layers of the implementation:
+
 
+
# Portage internals to handle "user/" and "group/" as special words. Would be treated almost identically to ebuilds up until actual merge time. Version specifiers, as well as USE flags, would not be allowed.
+
# Python-based code to parse user and group data in the profiles, and determine proper UID/GID to use on the system. This is the parsing and policy framework, and can be controlled by variables defined in <tt>make.conf</tt>/<tt>make.defaults</tt>. This would all be written in Python and integrated into the Portage core.
+
## "Core" Portage trees would use cascading profiles to define users and groups. This would allow variations based on architecture (Portage on MacOS X vs. Linux, for example.)
+
## Overlays would use <tt>OVERLAY_DIR/profiles/users</tt> and <tt>OVERLAY_DIR/profiles/groups</tt> to define user and group information required for the overlay. This way, overlays could extend users and groups.
+
# Python-based backwards-compatibility code (implementation to be determined)
+
# Profile-based plugin architecture, again python-based.
+
# <tt>user-add</tt> and <tt>group-add</tt> scripts, implemented as stand-alone executables (likely written as a shell script.) This is the only part not in python and these scripts do not do any kind of high-level policy decisions. They simply create the user or group and report success or failure.
+
 
+
=== Possible Changes and Unresolved Issues ===
+
 
+
==== Disable User/Group Creation ====
+
 
+
<tt>FEATURES="-auto-accounts"</tt> (<tt>auto-accounts</tt> would be enabled by default)
+
 
+
This is a change from GLEP 27 to get rid of ugly "no" prefix and to follow naming conventions for existing <tt>FEATURES</tt> settings.
+
 
+
With <tt>auto-accounts</tt> disabled, Portage will do an initial check using libc (respecting <tt>/etc/nsswitch.conf</tt>) to see if all depended-upon users and groups exist. If they exist, the user/group dependency will be satisfied and <tt>ebuild</tt> can continue. If the dependencies are not satisfied, then the ebuild will abort with unsatisfied dependencies and display the users and groups that need to be created, and what their associated settings should be.
+
 
+
==== Allow User/Group Names to Be Specified At Build Time ====
+
 
+
Some users may want an <tt>nginx</tt> user, while others may want a generic <tt>www</tt> user to be used.
+
 
+
TBD.
+
 
+
==== Not Elegant for Specific Users/Groups ====
+
 
+
This implementation looks cool but is potentially annoying for specific users and groups. For example, for an <tt>nginx</tt> ebuild that needs an <tt>nginx</tt> user, it would need to be added to the system profile. We probably need to implement ebuild-local user/groups as well.
+
 
+
==== Specify Required Users and Groups for Profile ====
+
 
+
Some users and groups '''must''' be part of the system and should be in the system set. It would be nice to move some of this out of baselayout and into the profiles directly. Maybe a good solution is to have <tt>baselayout</tt> <tt>RDEPEND</tt> on these users and groups.
+
 
+
TBD.
+
 
+
==== Dependency Prefix ====
+
 
+
One possible area of improvement is with the <tt>user/</tt> and <tt>group/</tt> syntax itself, which could be changed slightly to indicate that we are depending on something other than a package. But this is not absolutely necessary and "user" and "group" could be treated as reserved names that cannot be used for categories, since they have a special meaning.
+
 
+
==== .tbz2 support ====
+
 
+
In general, the design proposed above  will work well for binary packages, as long as the users and groups required by the <tt>.tbz2</tt> can be found in the local Portage tree and overlays. If not, then Portage will not have any metadata relating to the user(s) or group(s) that need to be created for the <tt>.tbz2</tt> and will not be able to create them, resulting in an install failure, which of course is not optimal.
+
 
+
Therefore, it may be necessary to embed user and group metadata within the <tt>.tbz2</tt> and have Portage use this data only if local user/group metadata for the requested users and groups is not available. In addition, this user/group metadata may need to be cached persistently inside <tt>/var/db/pkg</tt> or another location to ensure that it is continually available to the Portage UID/GID code. This could add a bit more complexity to the implementation but should solve the <tt>.tbz2</tt> failure problem. This would create three layers of user/group data:
+
 
+
# Core user/group metadata defined in <tt>/usr/portage</tt>.
+
# Overlay user/group metadata defined in <tt>OVERLAY_DIR/profiles/{users,groups}</tt>
+
# Package user/group metadata
+
 
+
Using pseudo-code, we could imagine resolution of user and group metadata at <tt>.tbz2</tt> install time to look like this:
+
 
+
<pre>
+
all_ug_metadata = profile_ug_metadata + overlay_ug_metadata
+
if (user_or_group in (all_ug_metadata)):
+
    return all_ug_metadata[user_or_group]
+
else:
+
    return binary_package_ug_metadata[user_or_group]
+
</pre>
+
==== Compatibility with other distributions ====
+
If our goal is to ensure a sane method of creating UID/GID's in packages, we should also look at making them compatible with the wider world.  The LSB http://refspecs.freestandards.org/LSB_3.0.0/LSB-Core-generic/LSB-Core-generic/usernames.html specifies very lax standards for system accounts.  Seemingly there are no hard standards for system/daemon UID/GID's, and no real desire in the community from anyone I discussed this issue with to standardize.  There is one important issue to note, and that is the lowest user account number.
+
* Fedora/RHEL:  Presently RHEL starts assigning UID/GID's to users of the system at 500 and moves up, this will changehttp://lists.fedoraproject.org/pipermail/devel/2011-May/151663.html to number after 1000
+
* Debian/Ubuntu: Presently Debian starts assigning UID/GID's to users of the system at 1000, and moves up.  This appears to be the standard distributions are moving towards
+
* Gentoo/Funtoo: Presently Funtoo and Gentoo are both compliant with Debian, and after Fedora 16, and the subsequent RHEL, this will be a standard across most major linux distributions.
+

Revision as of 16:50, 30 August 2011

Context

In some cases, it can be useful to set up an access on your Funtoo box such as a user:

  • does not see the whole contents of the machine but, instead, remains "jailed" in a home directory
  • is able to transfer files back and forth on the box via SFTP
  • does not have access to a shell

Such a SFTP only access is easy to setup:

  1. Assign a group (e.g. sftponly) to users that must be restricted to a SFTP-only account
  2. Change a bit the configuration of OpenSSH so that users belonging to your sftp-only group are given a chrooted access
  3. Make OpenSSH ignore any other command than running sftp-server on the server side for users belonging to your sftp-only group (this is where the trick lies !)

Quick start

First, a dedicated group must be created. For the sake of the example we use sftponly here, use whatever name fits your preferences:

# groupadd sftponly

Next in the configuration of OpenSSH (located in /etc/sshd/sshd_config) locate:

Subsystem      sftp    /usr/lib64/misc/sftp-server

and change it for:

Subsystem      sftp    internal-sftp

Now the $100 question: "how can OpenSSH can be told to restrict a user access to a simple sftp session?" Simple! Assuming that sftponly is the group you use for for your restricted users, just add to the file /etc/sshd/sshd_config the following statement:

# Restricted users, no TCP connexions bouncing, no X tunneling.
Match group sftponly
        ChrootDirectory /home/%u
        X11Forwarding no
        AllowTcpForwarding no
        ForceCommand internal-sftp

To understand how it works, you must be aware that, when you open an SSH session, the SSHD process launch a process on the server side which could be:

  • a shell => ssh login@host
  • a kind of dedicated ftp daemon (sftp-server) => sftp user@host

TBC