Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

This document explains how to create new shell (and email) accounts. See also doc/accounts to evaluate new account requests.

Note that this documentation needs work, as it overlaps with user-facing user management procedures (doc/accounts), see issue 40129.

Configuration

This should be done only once.

git clone db.torproject.org:/srv/db.torproject.org/keyrings/keyring.git account-keyring

It downloads the git repository that manages the OpenPGP keyring. This keyring is essential as it allows users to interact with the LDAP database securely to perform password changes and is also used to send the initial password for new accounts.

When cloning, you may get the following message (see tpo/tpa/team#41785):

fatal: detected dubious ownership in repository at '/srv/db.torproject.org/keyrings/keyring.git'

If this happens, you need to run the following command as your user on db.torproject.org:

git config --global --add safe.directory /srv/db.torproject.org/keyrings/keyring.git

Creating a new user

This procedure can be used to create a real account for a human being. If this is for a machine or another automated thing, create a role account (see below).

To create a new user, specific information need to be provided by the requester, as detailed in doc/accounts.

The short version is:

  1. Import the provided key to your keyring. That is necessary for the script in the next point to work.

  2. Verify the provided OpenPGP key

    It should be signed by a trusted key in the keyring or in a message signed by a trusted key. See doc/accounts when unsure.

  3. Add the OpenPGP key to the account-keyring.git repository and create the LDAP account:

    FINGERPRINT=0123456789ABCDEF0123456789ABCDEF01234567 &&
    NEW_USER=alice &&
    REQUESTER="bob in ticket #..." &&
    ./NEW "$FINGERPRINT" "$NEW_USER" &&
    git add torproject-keyring/"${NEW_USER}-${FINGERPRINT}.gpg" &&
    git commit -m"new user ${NEW_USER} requested by ${REQUESTER}" &&
    git push &&
    ssh -tt $USER@alberti.torproject.org "ud-useradd -n && sudo -u sshdist ud-generate && sudo -H ud-replicate"
    

The last line will create the user on the LDAP server. See below for detailed information on that magic instruction line, including troubleshooting.

Note that $USER, in the above, shouldn't be explicitly expanded unless your local user is different from your alberti user. In my case, $USER, locally, is anarcat and that is how I login to alberti as well.

Notice that when prompted for whom to add (a GPG search), enter the full $FINGERPRINT verified above

What followed are detailed, step-by-step instructions, to be performed after the key was added to the account-keyring.git repository (up to the git push step above).

on the LDAP server

Those instructions are a copy of the last step of the above instructions, provided to clarify what each step does. Do not follow this procedure and instead follow the above.

The LDAP server is currently alberti. Those steps are supposed to be ran as a regular user with LDAP write access.

  1. create the user:

    ud-useradd -n
    

    This command asks a bunch of questions interactively that have good defaults, mostly taken from the OpenPGP key material, but it's important to review them anyways. in particular:

    • when prompted for whom to add (a GPG search), enter the full $FINGERPRINT verified above

    • the email forward is likely to be incorrect if the key has multiple email address as UIDs

    • the user might already be present in the Postfix alias file (tor-puppet/modules/postfix/files/virtual) - in that case, use that email as the Email forwarding address if present and remove it from Puppet

  2. synchronize the change:

     sudo -u sshdist ud-generate && sudo -H ud-replicate
    

on other servers

This step is optional and can be used to force replication of the change to another server manually.

  1. synchronize the change:

    sudo -H ud-replicate
    
  2. run puppet:

    sudo puppet agent -t
    

Creating a user without a PGP key

In most cases we want to use the person's PGP key to associate with their new LDAP account, but in some cases it may be difficult to get a person to generate a PGP key (and most importantly, keep managing that key effectively afterwards) and we might still want to grant the person an email account.

For those cases, it's possible to create an LDAP account without associating it to a PGP key.

First, generate a password and note it down somewhere safe temporarily. Then generate a hash for that password and noted it down. If you don't have this command on your computer, you can run that on alberti:

mkpasswd -m bcrypt-a

On alberti, find a free user ID with fab user.list-gaps (more information on that command in the creating a role section)

Then, on alberti, connect to ldapvi and at the end of the file add something like the following. Make sure to modify uid=[...] and all UID and GID numbers and then the user's cn and sn fields to values that make sense for your case and replace the value of mailPassword with the password hash you noted down earlier. Keep the userPassword as-is since it will tell LDAP to lock the LDAP account:

add gid=exampleuser,ou=users,dc=torproject,dc=org
gid: exampleuser
gidNumber: 15xx
objectClass: top
objectClass: debianGroup

add uid=exampleuser,ou=users,dc=torproject,dc=org
uid: exampleuser
objectClass: top
objectClass: inetOrgPerson
objectClass: debianAccount
objectClass: shadowAccount
objectClass: debianDeveloper
uidNumber: 15xx
gidNumber: 15xx
gecos: exampleuser,,,,
cn: Example
sn: User
userPassword: {crypt}$LK$
mailPassword: <REDACTED>
emailForward: <address>
loginShell: /bin/bash
mailCallout: FALSE
mailContentInspectionAction: reject
mailGreylisting: FALSE
mailDefaultOptions: FALSE

Save and exit and you should get prompted about adding two entries.

Lastly, refresh and resync the user database:

  • On alberti: sudo -u sshdist ud-generate && sudo -H ud-replicate
  • On submit-01 as root: ud-replicate

The final step is then to contact the person on Signal and send them the password in a disappearing message.

troubleshooting

If the ud-useradd command fails with this horrible backtrace:

Updating LDAP directory..Traceback (most recent call last):
  File "/usr/bin/ud-useradd", line 360, in <module>
    lc.add_s(Dn, Details)
  File "/usr/lib/python3/dist-packages/ldap/ldapobject.py", line 236, in add_s
    return self.add_ext_s(dn,modlist,None,None)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3/dist-packages/ldap/ldapobject.py", line 222, in add_ext_s
    resp_type, resp_data, resp_msgid, resp_ctrls = self.result3(msgid,all=1,timeout=self.timeout)
                                                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3/dist-packages/ldap/ldapobject.py", line 543, in result3
    resp_type, resp_data, resp_msgid, decoded_resp_ctrls, retoid, retval = self.result4(
                                                                           ^^^^^^^^^^^^^
  File "/usr/lib/python3/dist-packages/ldap/ldapobject.py", line 553, in result4
    ldap_result = self._ldap_call(self._l.result4,msgid,all,timeout,add_ctrls,add_intermediates,add_extop)
                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3/dist-packages/ldap/ldapobject.py", line 128, in _ldap_call
    result = func(*args,**kwargs)
             ^^^^^^^^^^^^^^^^^^^^
ldap.INVALID_SYNTAX: {'msgtype': 105, 'msgid': 6, 'result': 21, 'desc': 'Invalid syntax', 'ctrls': [], 'info': 'sn: value #0 invalid per syntax'}

... it's because you didn't fill the form properly. In this case, the sn field ("Last name" in the form) was empty. If you don't have a second name, just reuse the first name.

Creating a role

A "role" account is like a normal user, except it's for machines or services, not real people. It's useful to run different services with different privileges and isolation.

Here's how to create a role account:

  1. Do not use ud-groupadd and ud-roleadd. They are partly broken.

  2. Run fab user.list-gaps from a clone of the fabric-tasks repository on alberti.tpo to find an unused uidNumber/gidNumber pair.

    • Make sure the numbers match. If you are unsure, find the highest uidNumber / gidNumber pair, increment that and use it as a number. You must absolutely make sure the number is not already in use.
    • the fabric task connects directly to ldap, which is firewalled from the exterior, so you won't be able to run the task from your computer.
  3. On LDAP host (currently alberti.tpo), as a user with LDAP write access, do:

     ldapvi -ZZ --encoding=ASCII --ldap-conf -h db.torproject.org -D uid=${USER},ou=users,dc=torproject,dc=org
    
  4. Create a new group role for the new account:

    • Copy-paste a previous gid that is also a debianGroup
    • Change the first word of the copy-pasted block to add instead of the integer
    • Change the cn (first line) to the new group name
    • Change the gid: field (last line) to the new group name
    • Set the gidNumber to the number found in step 2
  5. Create the actual user role:

    • Copy-paste a previous uid role entry (with a objectClass: debianRoleAccount).
    • Change the first word of the copy-pasted block to add instead of the integer
    • Change the uid=, uid:, gecos: and cn: lines.
    • Set the gidNumber and uidNumber to the number found in step 2
    • If you need to set a mail password you can generate a blowcrypt password with python (search for example of how to do this). Change the hash identifier to $2y$ instead of $2b$.
  6. Add the role to the right host:

    • Add a allowedGroups: NEW-GROUP line to host entries that should have this role account deployed.
    • If the role account will only be used for sending out email by connecting to submission.torproject.org, the account does not need to be added to a host.
  7. Save the file, and accept the changes

  8. propagate the changes from the LDAP host:

     sudo -u sshdist ud-generate && sudo -H ud-replicate
    
  9. (sometimes) create the home directory on the server, in Puppet:

     file { '/home/bridgescan':
       ensure => 'directory',
       mode   => '0755',
       owner  => 'bridgescan',
       group  => 'bridgescan';
     }
    

Sometimes a role account is made to start services, see the doc/services page for instructions on how to do that.

Sudo configuration

A user will often need to more permissions than its regular scope. For example, a user might need to be able to access a specific role account, as above, or run certain commands as root.

We have sudo configuration that enable us to give piecemeal accesses like this. We often give accesses to groups instead of specific users for easier maintenance.

Entries should be added by declaring a sudo::conf resource in the relevant profile class in Puppet. For example:

sudo::conf { 'onbasca':
  content =>  @(EOT)
	# This file is managed by Puppet.
	%onbasca     ALL=(onbasca)      ALL
	| EOT
}

An alternative to this which avoids the need to create a profile class containing a single sudo::conf resource is to add the configuration to Hiera data. The equivalent for the above would be placing this YAML snippet at the role (preferably) or node hierarchy:

profile::sudo::configs:
  onbasca:
    content: |
      # This file is managed by Puppet.
      %onbasca     ALL=(onbasca)      ALL

Sudo primer

As a reminder, the sudoers file syntax can be distilled to this:

FROMWHO HOST=(TOWHO) COMMAND

For example, this allows the group wheel (FROMWHO) to run the service apache reload COMMAND as root (TOWHO) on the HOST example:

%wheel example=(root) service apache reload

The HOST, TOWHO and COMMAND entries can be set to ALL. Aliases can also be defined and many more keywords. In particular, the NOPASSWD: prefix before a COMMAND will allow users to sudo without entering their password.

Granting access to a role account

That being said, you can simply grant access to a role account by adding users in the role account's group (through LDAP) then adding a line like this in the sudoers file:

%roleGroup example=(roleAccount) ALL

Multiple role accounts can be specified. This is a real-world example of the users in the bridgedb group having full access to the bridgedb and bridgescan user accounts:

%bridgedb		polyanthum=(bridgedb,bridgescan)			ALL

Another real-world example, where members of the %metrics group can run two different commands, without password, on the STATICMASTER group of machines, as the mirroradm user:

%metrics		STATICMASTER=(mirroradm)	NOPASSWD: /usr/local/bin/static-master-update-component onionperf.torproject.org, /usr/local/bin/static-update-component onionperf.torproject.org

Update a user's GPG key

The account-keyring repository contains an update script ./UPDATE which takes the ldap username as argument and automatically updates the key.

If you /change/ a user's key (to a new primary key), you also need to update the user's keyFingerPrint attribute in LDAP.

After updating a key in the repository, the changes must be pushed to the remote hosted on the LDAP server.

Other documentation

Note that a lot more documentation about how to manage users is available in the LDAP documentation.