1

I’m new to using OpenSSH for Windows (Server 2019) and I can’t find the answer I need...

I am trying see if I can configure OpenSSH so different users have access to different directories.

Basically my client is going to be using OpenSSH for their suppliers to send data via it. For the most part they will all be going into one directory with the exception of one which needs to go to another (via UNC path if possible).

Does anyone know if this be possible?

3 Answers 3

3

Yes you can. Similar like below

ForceCommand internal-sftp
Subsystem sftp sftp-server.exe -d "C:\"

#Match User User1
#ChrootDirectory D:\Data1

Match User User2
ChrootDirectory D:\Data2

Match User User3
ChrootDirectory D:\Data3

Match User User4
ChrootDirectory D:\Data4

Here User1,User2,User3 are local users to machine or AD user

You can even create user group and grand permission

Match Group SFTPUsers
ChrootDirectory D:\Data
1

For those who struggled hours like I did, take care, there are some conditions:

  1. user names must be in lower case
  2. directory must be owned by the user or his group

For example, no success to a subdirectory of c:\Users\Public.

Note that you can also put several users accounts separated by a comma and no whitespace, or even use wildcards (*, ?) to share the same directory:

  • example 1: Match User user1,user2
  • example 2: Match User "user*"
0

To "lock" (aka "jail") users to specific directories in Windows OpenSSH/SFTP, try this in sshd_config:

Subsystem   sftp    sftp-server.exe
ForceCommand internal-sftp

#   Optional: only allow specific users / groups to use the system:
# AllowUsers ftp_user1 ftp_user2 ftp_user3 administrator
#   ... and/or:
# AllowGroups ftp_users administrators

Match User ftp_user1
    ChrootDirectory %h\Documents\

Match User ftp_user2
    ChrootDirectory %h\

Match User ftp_user3,ftp_user4,ftp_user5*
    ChrootDirectory C:\SFTP\Shared\

The above will:

  1. "Lock" ftp_user1 to their <home directory>\Documents\ folder
  2. "Lock" ftp_user2 to their <home directory>\ folder
  3. "Lock" ftp_user3, ftp_user4, and any user matching ftp_user5* to C:\SFTP\Shared\

P.S. In my experiments, openssh/sftp starts behaving unpredictably and denying access to pretty much everyone (but not always!) with the mere presence of a Match Group directive in sshd_config - even for user accounts not affected by the directive. (Tried on two WS2019 VMs.) So I'd stay away from it and use Match User (with wildcards if needed) instead.


tl;dr (it's long):

Basically my client is going to be using OpenSSH for their suppliers to send data via it.

I am assuming this means using the sftp subsystem of OpenSSH - which is a good thing, as that subsystem seems to be only one allowing "jailing" users to specific directories via a ChrootDirectory directive:

ChrootDirectory (Support added in v7.7.0.0)

This directive is only supported with sftp sessions. A remote session into cmd.exe wouldn't honor the ChrootDirectory. To set up a sftp-only chroot server, set ForceCommand to internal-sftp. You may also set up scp with chroot, by implementing a custom shell that would only allow scp and sftp.

If sftp is what you need, then it works (somewhat) - as mentioned in other answers - yet for me, the directive seems to break something when combined with the Match Group directive, and behave unpredictably. Without it, Windows' standard OpenSSH/SFTP feature is of limited use (to us), and makes it harder (and less secure) to maintain.

Consider the following super-simple setup on an otherwise clean and fully patched AD-joined Windows Server 2019, where OpenSSH was installed following Get started with OpenSSH for Windows instructions from Microsoft.

The following changes were made to C:\ProgramData\ssh\sshd_config:

SyslogFacility LOCAL0
LogLevel DEBUG3

(the above is simply to log verbosely to files rather than to Windows Events)

ForceCommand internal-sftp
Subsystem   sftp    sftp-server.exe -d %d

(The above forces any ssh user to only use sftp and specifies the "subsystem" for sftp, i.e. the sftp-server.exe command with the optional -d %d flag that starts that command in the user's home directory - see OpenBSD manual.)

A local user test was created and added to the standard users group. Note that there's no ChrootDirectory in the configuration yet.

Next, I make 4-5 attempts to sftp to the server using the sftp command via PowerShell from my Win11 desktop and they're all successful. The user lands in their Windows user directory thanks to the -d %d flag in the Subsystem directive, and is able to navigate around the full file system, i.e. the user is not jailed to the home directory:

PS> sftp test@sftpserver
test@sftpserver's password:
Connected to sftpserver.
sftp> pwd
Remote working directory: /C:/Users/test
sftp> ls ..
../user1         ../user2       ../All Users     ../Default
../Default User  ../Public      ../user3         ../desktop.ini
../test          ../user4
sftp> quit

Next, we add the ChrootDirectory directive (and restart sshd service on the server):

Match Group users,administrators
    ChrootDirectory C:\Users\

Several sftp sessions in a row, with no changes to the client or to the server:

PS> sftp test@sftpserver
test@sftpserver's password:
Connected to sftpserver.
sftp> pwd
Remote working directory: /
sftp> ls
user1          user2        All Users   Default
Default User   Public       user3       desktop.ini
test           user4
sftp> exit

PS> sftp test@sftpserver
test@sftpserver's password:
client_loop: send disconnect: Connection reset
Connection closed

PS> sftp test@sftpserver
test@sftpserver's password:
Permission denied, please try again.
test@sftpserver's password:
Permission denied, please try again.
test@sftpserver's password:
Connection closed

Note that in all 3 attempts, the server's responses are different:

  1. Successful login in the 1st attempt, and the user appears to have been successfully "jailed" to C:\Users\ (yay!).
  2. client_loop: send disconnect: Connection reset and Connection closed on the 2nd attempt.
  3. Permission denied on the 3rd (and most subsequent) attempts

Digging into the logs:

5920 2024-02-28 12:45:13.237 debug3: fd 5 is not O_NONBLOCK
5920 2024-02-28 12:45:13.237 debug3: spawning "C:\\Windows\\System32\\OpenSSH\\sshd.exe" "-R"
[... skipping a bunch of lines that don't tell me much ...]
4536 2024-02-28 12:45:13.331 Connection from <source_ip> port 65442 on <this_server_ip> port 22
4536 2024-02-28 12:45:13.331 debug1: Client protocol version 2.0; client software version OpenSSH_for_Windows_8.6
4536 2024-02-28 12:45:13.331 debug1: match: OpenSSH_for_Windows_8.6 pat OpenSSH* compat 0x04000000
4536 2024-02-28 12:45:13.331 debug1: Local version string SSH-2.0-OpenSSH_for_Windows_7.7
4536 2024-02-28 12:45:13.331 debug2: fd 3 setting O_NONBLOCK
4536 2024-02-28 12:45:13.393 debug3: spawning "C:\\Windows\\System32\\OpenSSH\\sshd.exe" "-y"
4536 2024-02-28 12:45:13.409 debug2: Network child is on pid 6016
[...]
4536 2024-02-28 12:45:13.487 debug3: receive packet: type 5 [preauth]
4536 2024-02-28 12:45:13.487 debug3: send packet: type 6 [preauth]
4536 2024-02-28 12:45:13.487 debug3: receive packet: type 50 [preauth]
4536 2024-02-28 12:45:13.487 debug1: userauth-request for user test service ssh-connection method none [preauth]

Note user test in the last line above. Continuing:

4536 2024-02-28 12:45:13.487 debug1: attempt 0 failures 0 [preauth]
4536 2024-02-28 12:45:13.487 debug3: mm_getpwnamallow entering [preauth]
4536 2024-02-28 12:45:13.487 debug3: mm_request_send entering: type 8 [preauth]
4536 2024-02-28 12:45:13.487 debug3: mm_getpwnamallow: waiting for MONITOR_ANS_PWNAM [preauth]
4536 2024-02-28 12:45:13.487 debug3: mm_request_receive_expect entering: type 9 [preauth]
4536 2024-02-28 12:45:13.487 debug3: mm_request_receive entering [preauth]
4536 2024-02-28 12:45:13.487 debug3: mm_request_receive entering
4536 2024-02-28 12:45:13.487 debug3: monitor_read: checking request 8
4536 2024-02-28 12:45:13.487 debug3: mm_answer_pwnamallow
4536 2024-02-28 12:45:13.487 debug2: parse_server_config: config reprocess config len 398
4536 2024-02-28 12:45:13.487 debug3: checking match for 'Group users,administrators' user test host <source_IP> addr <source_IP> laddr <this_server_IP> lport 22
4536 2024-02-28 12:45:13.487 debug3: LsaLogonUser Succeeded (Impersonation: 0)
4536 2024-02-28 12:45:13.487 debug2: get_user_groups: extracting all groups of user test
4536 2024-02-28 12:45:13.487 debug3: Added group 'none' for user test
4536 2024-02-28 12:45:13.487 debug3: Added group 'sftp_users' for user test
4536 2024-02-28 12:45:13.487 debug3: Added group 'users' for user test
4536 2024-02-28 12:45:13.487 debug2: get_user_groups: done extracting all groups of user test
4536 2024-02-28 12:45:13.487 debug1: user  matched group list users,administrators at line 95

Note two spaces in the last line above in user matched group, and no username. Compare to a similar line from a successful (previous) logon:

7460 2024-02-28 12:44:41.670 debug1: user test matched group list users,administrators at line 95

Continuing:

4536 2024-02-28 12:45:13.487 debug3: match found
4536 2024-02-28 12:45:13.487 debug3: reprocess config:96 setting ChrootDirectory C:\\Users\\
4536 2024-02-28 12:45:13.487 debug3: checking match for 'Group administrators' user  host <source_IP> addr <source_IP> laddr <this_server_IP> lport 22
4536 2024-02-28 12:45:13.487 debug3: get_passwd: Invalid account type: 3.
4536 2024-02-28 12:45:13.487 debug1: Can't match group at line 106 because user  does not exist
4536 2024-02-28 12:45:13.487 debug3: match not found
4536 2024-02-28 12:45:13.487 debug3: mm_answer_pwnamallow: sending MONITOR_ANS_PWNAM: 0
4536 2024-02-28 12:45:13.487 debug3: mm_request_send entering: type 9
4536 2024-02-28 12:45:13.487 debug2: monitor_read: 8 used once, disabling now
4536 2024-02-28 12:45:13.487 debug3: mm_inform_authserv entering [preauth]
4536 2024-02-28 12:45:13.487 debug3: mm_request_send entering: type 4 [preauth]
4536 2024-02-28 12:45:13.487 debug2: input_userauth_request: try method none [preauth]
4536 2024-02-28 12:45:13.487 debug3: userauth_finish: failure partial=0 next methods="publickey,password,keyboard-interactive" [preauth]
4536 2024-02-28 12:45:13.487 debug3: send packet: type 51 [preauth]
4536 2024-02-28 12:45:13.487 debug3: mm_request_receive entering
4536 2024-02-28 12:45:13.487 debug3: monitor_read: checking request 4
4536 2024-02-28 12:45:13.487 debug3: mm_answer_authserv: service=ssh-connection, style=
4536 2024-02-28 12:45:13.487 debug2: monitor_read: 4 used once, disabling now
4536 2024-02-28 12:45:13.503 debug3: receive packet: type 50 [preauth]
4536 2024-02-28 12:45:13.503 debug1: userauth-request for user test service ssh-connection method keyboard-interactive [preauth]
4536 2024-02-28 12:45:13.503 debug1: attempt 1 failures 0 [preauth]
4536 2024-02-28 12:45:13.503 debug2: input_userauth_request: try method keyboard-interactive [preauth]
4536 2024-02-28 12:45:13.503 debug1: keyboard-interactive devs  [preauth]
4536 2024-02-28 12:45:13.503 debug1: auth2_challenge: user=test devs= [preauth]
4536 2024-02-28 12:45:13.503 debug1: kbdint_alloc: devices '' [preauth]
4536 2024-02-28 12:45:13.503 debug2: auth2_challenge_start: devices  [preauth]
4536 2024-02-28 12:45:13.503 debug3: userauth_finish: failure partial=0 next methods="publickey,password,keyboard-interactive" [preauth]
4536 2024-02-28 12:45:13.503 debug3: send packet: type 51 [preauth]

In other words (and in my experience so far), When combined with Match Group, ChrootDirectory breaks things - badly, to a point of making sftp unusable.

P.S. When using Match User directive rather than Match Group, all seems to work.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .