Extra SSH/SFTP/FTPS accounts per client
By default an Octopus instance has one tenant account pair (oN / oN.ftp).
BOA can provision one extra subaccount per Ægir Client — a separate
SSH/SFTP/FTPS account scoped to only that client's sites. The Unix-account side is
driven by manage_ltd_users.sh (its _manage_sec secondary-user pass, iterating
the main tenant's clients/ directories); the Ægir-side trigger is site
ownership — a site node whose Client field points at the Client. Provision
maintains the clients/<client>/<uri> symlinks the Unix side consumes.
The granularity is per Client, not per individual developer: each Ægir Client maps to exactly one shell subaccount, shared by everyone assigned to that Client. To give two people independent shell credentials, give each their own Ægir Client (see the accountability discussion below).
The Client + subaccount model
Octopus instance o1
└── main account: o1 + o1.ftp ← operator + primary tenant
│
└── Aegir Client "AcmeCorp"
│ ├── subaccount: o1.acme ← one shell account for this Client
│ └── sites: acme-prod.example.com
│ acme-staging.example.com
│
└── Aegir Client "WidgetCo"
├── subaccount: o1.widget
└── sites: widget.example.com
Each subaccount has its own SSH key and password, its own lshell config, sees only sites belonging to its parent Client, and cannot touch other Clients' sites within the same Octopus.
Provisioning lifecycle
A subaccount is requested by making the Client the owner of at least one
site — the Client field on the site node, not the Client's Users list. On the
site's next Verify/Install, Provision creates the
~/clients/<client>/<uri> symlink in the main tenant account
(_provision_client_create_symlink); within a few minutes the cron-driven
manage_ltd_users.sh (the */3 root crontab entry) finds a client directory
with at least one resolving site symlink and creates the Unix account. A Client
owning no surviving sites gets no account — _manage_sec rmdir's emptied
client directories, after which _kill_zombies removes the orphaned account.
The subaccount username format is <octopus>.<client> — e.g. o1.acme — where
<client> is the Client's directory name reduced to lowercase alphanumerics. It
carries no .ftp suffix (unlike the main oN.ftp tenant) and no
per-developer segment.
What the subaccount can do
Same as the main oN.ftp account (lshell-restricted), but scoped to the sites
of one Client:
cdinto the Drupal codebase directories belonging to that Client's sites.- Run
vdrush/composeragainst those sites only. - SSH key + SFTP file upload for those site directories.
It cannot see other Clients' sites, touch the host system, or bypass lshell.
Per-subaccount SSH keys
The subaccount's ~/.ssh/authorized_keys is at the standard path
(home is /home/<oN>.<client>, mirroring the username):
/home/<oN>.<client>/.ssh/authorized_keys
The owner (or a manager with shell access) populates it via SFTP or by editing directly.
Password rotation
Subaccounts carry the same 90-day password expiry as the main oN.ftp account
(passwd -w 7 -x 90 at creation). Nothing is emailed anywhere: the generated
password is written once to /home/<oN>.ftp/users/<oN>.<client> in the main
tenant account, and forwarding it to the developer is the tenant's job.
Rotation is file-driven — deleting that marker file makes _kill_zombies
remove the account on the next */3 run and _manage_sec recreate it with a
fresh password, written back to the same file. The rebuild removes the
subaccount's home, including its ~/.ssh/authorized_keys.
Removal lifecycle
Removing the user from the Client's Users list, or otherwise breaking the
subaccount's state, is what triggers deletion — there is no dedicated removal
Task. manage_ltd_users.sh's _kill_zombies pass (run from the */3 cron)
detects a subaccount whose state has diverged and deletes it. State is considered
diverged when any of these is true: the ~/sites symlink is missing, its target
no longer exists, or the /home/<oN>.ftp/users/<oN>.<client> marker file is gone.
Deletion is immediate on that detection — there is no grace period and
nothing configurable about the timing. The account is removed with deluser --remove-home, and the deleted home is written once as a backup tarball under
/var/backups/zombie/deleted/<timestamp>/. That tarball is an archival copy for
recovery, not a retained or reactivatable home — the live account and its home are
gone at that point. A second sweep catches orphaned home directories with no
matching passwd entry and moves them to the same backup location as
.leftover-<name>.
Why per-Client subaccounts vs. a shared oN.ftp
Sharing the main oN.ftp account across customers carries operational risks that
per-Client subaccounts remove:
- Cross-Client snooping —
oN.ftpcan see every site on the instance, whereas a Client subaccount sees only its own Client's sites. - Blast radius on disable — rotating or disabling
oN.ftpaffects everyone; a Client subaccount can be retired independently.
This is BOA's preferred pattern for the agency model (one Octopus, one Ægir Client per customer, one shell subaccount per Client).
Because the shell account is per Client, not per person, individual-developer
accountability is achieved by mapping one Ægir Client per individual — each
person then gets their own oN.<client> account, key and log identity. Putting
several developers under a single Client gives them a shared subaccount, so the
log shows oN.<client> did X, not which person — the same accountability gap as
sharing oN.ftp, just narrowed to one Client's sites.
Related
- lshell + manage_ltd_users — the lshell layer per account and the orchestrator that creates/removes these subaccounts.
- Security model — the broader multi-tenant model.
- SSH + SFTP hardening — the SSH server config that applies to every subaccount.