Two separate permissions get confused in this design: who may use a directory, and which addresses may connect at all. Writing an account list next to a source list feels like a pairing, and Samba evaluates the two lists as independent predicates instead. Anyone who satisfies both is admitted, which is a wider rule than most administrators intend. The fix is either a different share layout or a policy layer that can express a pair.

The myth: two lists make one rule

The smb.conf reference defines valid users and hosts allow as two unrelated parameters, and setting both on one share is where the confusion starts. Set valid users = mira, tomas and hosts allow = 10.77.0.21, 10.77.0.22 and four combinations are admitted: Mira from either address, Tomas from either address. Nothing in the configuration ties the first name to the first address, and the order the names appear in has no meaning. Reordering them, adding a second hosts allow line or putting one name per line changes nothing. The result is the product of two lists, not a mapping between them.

What most people actually want is narrower: Mira only from one address, Tomas only from another. A single share definition has no syntax for that binding, and no amount of clever ordering creates one. That is a policy shape Samba does not express inside one share, which is where the myth survives.

What Samba evaluates, in order

The stages run in a fixed order, and getting the order wrong produces the wrong diagnosis. A client opens a connection; a global host check runs if one is configured; session setup authenticates the account; the client then connects to a share, where that share's own host list, valid users and read-only setting are applied; only when a file is opened do the filesystem permissions of the authenticated identity decide. Each stage can reject the request on its own, and passing one says nothing about the next. The important consequence is that the account is known by the time the share's host list is evaluated, and the host list still has no way to name that account.

That is why a share cannot bind one user to one address: hosts allow and valid users are separate predicates applied at the same tree-connect stage, each answering its own question. The host predicate never takes the account name as an input, so it cannot be written per user. Only a second share, or a policy layer outside Samba, can express the pair.

Global values matter because of how they are inherited. A share that omits hosts allow inherits the global value, and a share that sets its own uses that value in place of the global one rather than in addition to it. So a broad global list widens the shares that never set the parameter, and does nothing to a share that defines its own. Run testparm -s and read the per-share values it prints, because that output is the effective policy and the file on disk is not.

Narrowing one share also does nothing for the rest of the server. Other shares, printer queues and the IPC connection keep their own rules, so a design that relies on one share's address list protects one path rather than the host.

Two share names over one directory

When the same directory has to be reachable by two people from two addresses, expose it twice under different names. Each stanza carries one account and one source, which turns the pairing into something the configuration can enforce. The cost is that each person uses a different share name to reach the same files, and that has to be a deliberate client-side change.

[shared-mira]
    path = /srv/shared
    guest ok = no
    read only = yes
    valid users = mira
    hosts allow = 10.77.0.21
    hosts deny = ALL

[shared-tomas]
    path = /srv/shared
    guest ok = no
    read only = yes
    valid users = tomas
    hosts allow = 10.77.0.22
    hosts deny = ALL

A share gate is not a filesystem permission. Both identities still need access to the directory through the underlying ACL, and the share's read-only setting is a second layer rather than the only one. Grant write access only if the workflow requires it, then check that files created by one identity stay usable by the other.

Both stanzas point at one path, so a change to the directory affects both shares. That is usually the point, but it also means a permission mistake is visible to both users at once.

What the source address can prove

The address Samba sees is the address that reached the server, not the address the client believes it has. A VPN gateway that translates every client to one source address collapses the distinction: all clients arrive as the same host and the source list can no longer tell them apart. Stable per-client allocation and a gateway that preserves the source are prerequisites for using addresses as policy at all. Without both, the restriction measures the gateway rather than the user.

Even then, an address is not a device. Two laptops behind one tunnel address, a reassigned lease and a shared workstation all defeat the assumption that a source identifies a person. Treat the restriction as network scope and keep authenticated SMB accounts as the identity layer, with a device credential such as a certificate if you need device authorization.

This is also why the design gates shares and not the server. A refused pair can still open a session, reach another share or list the server, which is a different outcome from an authentication failure at connect time. If the requirement is rejection before authentication, Samba's share parameters are the wrong tool.

Prove it with crossed pairs

Validate the configuration, then test the four combinations with fresh connections rather than a reused session. The intended pair should reach the directory, and both crossed pairs should fail. A cached session from an earlier test will happily answer for the wrong combination and make a broken rule look correct.

testparm -s
smbclient //fileserver/shared-mira -U mira -c 'ls'
smbclient //fileserver/shared-tomas -U tomas -c 'ls'
smbstatus --shares

A syntax check only proves the file parses. Credentials, ACLs and the host decision are exercised by an actual connection, so run the client command from the address you mean to test and watch which stage refuses it. If a crossed pair succeeds, the effective host list is wider than the stanza suggests, and testparm -s is the first thing to read. If a crossed pair fails for the wrong reason, the account or the ACL is the suspect.

Monitoring sessions with smbstatus and killing them is a response action taken after access has started, not admission control. Blocking an entire observed address can also disconnect unrelated users behind the same NAT, which is why the decision belongs in the share definition and the monitoring belongs in the response process. Keep the two separate and the design stays honest about what it enforces.

Nsɛm a ɛfa ho