AndrewBartlett 1 October 2002 The Upcoming SAM System Security in the 'new SAM' One of the biggest problems with passdb is it's implementation of 'security'. Access control is on a 'are you root at the moment' basis, and it has no concept of NT ACLs. Things like ldapsam had to add 'magic' 'are you root' checks. We took this very seriously when we started work, and the new structure is designed with this in mind, from the ground up. Each call to the SAM has a NT_TOKEN and (if relevant) an 'access desired'. This is either provided as a parameter, or implicitly supplied by the object being accessed. For example, when you call < NTSTATUS sam_get_account_by_name(const SAM_CONTEXT *context, const NT_USER_TOKEN *access_token, uint32 access_desired, const char *domain, const char *name, SAM_ACCOUNT_HANDLE **account) The context can be NULL (and is used to allow import/export by setting up 2 contexts, and allowing calls on both simultaneously) The access token *must* be specified. Normally the user's token out of current_user, this can also be a global 'system' context. The access desired is as per the ACL, for passing to the seaccess stuff. The domain/username are standard. Even if we only have one domain, keeping this ensures that we don't get 'unqualified' usernames (same problem as we had with unqualified SIDs). We return a 'handle'. This is opaque to the rest of Samba, but is operated on by get/set routines, all of which return NTSTATUS. The access checking is done by the SAM module. The reason it is not done 'above' the interface is to ensure a 'choke point'. I put a lot of effort into the auth subsystem to ensure we never 'accidentally' forgot to check for null passwords, missed a restriction etc. I intend the SAM to be written with the same caution. The reason the access checking is not handled by the interface itself is due to the different implementations it make take on. For example, on ADS, you cannot set a password over a non-SSL connection. Other backends may have similar requirements - we need to leave this policy up to the modules. They will naturally have access to 'helper' procedures and good examples to avoid mishaps. (Furthermore, some backends my actually chose to push the whole ACL issue to the remote server, and - assuming ldap for this example - bind as the user directly) Each returned handle has an internal 'access permitted', which allows the 'get' and 'set' routines to return 'ACCESS_DENIED' for things that were not able to be retrieved from the backend. This removes the need to specify the NT_TOKEN on every operation, and allows for 'object not present' to be easily distinguished from 'access denied'. When you 'set' an object (calling sam_update_account) the internal details are again used. Each change that has been made to the object has been flagged, so as to avoid race conditions (on unmodified components) and to avoid violating any extra ACL requirements on the actual data store (like the LDAP server). Finally, we have generic get_sec_desc() and set_sec_desc() routines to allow external ACL manipulation. These do lookups based on SID. Standalone from UNIX One of the primary tenants of the 'new SAM' is that it would not attempt to deal with 'what unix id for that'. This would be left to the 'SMS' (Sid Mapping System') or SID farm, and probably administered via winbind. We have had constructive discussion on how 'basic' unix accounts like 'root' would be handled, and we think this can work. Accounts not preexisting in unix would be served up via winbind. This is an *optional* part, and my preferred end-game. We have a fare way to go before things like winbind up to it however. Handles and Races in the new SAM One of the things that the 'new SAM' work has tried to face is both compatibility with existing code, and a closer alignment to the SAMR interface. I consider SAMR to be a 'primary customer' to the this work, because if we get alignment with that wrong, things get more, rather than less complex. Also, most other parts of Samba are much more flexible with what they can allow. In any case, that was a decision taken as to how the general design would progress. BTW, my understanding of SAMR may be completely flawed. One of the most race-prone areas of the new code is the conflicting update problem. We have taken two approaches: 'Not conflicting' conflicts. Due to the way usrmgr operates, it will open a user, display all the properties and *save* them all, even if you don't change any. For this, see what I've done in rpc_server/srv_samr_util.c. I intend to take this one step further, and operate on the 'handle' that the values were read from. This should mean that we only update things that have *really* changed. 'conflicting' updates: Currently we don't deal with this (in passdb or the new sam stuff), but the design is sufficiently flexible to 'deny' a second update. I don't foresee locking records however. Layers Application This is where smbd, samtest and whatever end-user replacement we have for pdbedit sits. They use only the SAM interface, and do not get 'special knowledge' of what is below them. SAM Interface This level 'owns' the various handle structures, the get/set routines on those structures and provides the public interface. The application layer may initialize a 'context' to be passed to all interface routines, else a default, self-initialising context will be supplied. This layser finds the appropriate backend module for the task, and tries very hard not to need to much 'knowledge'. It should just provide the required abstraction to the modules below, and arrange for their initial loading. We could possibly add ACL checking at this layer, to avoid discrepancies in implementation modules. SAM Modules These do not communicate with the application directly, only by setting values in the handles, and receiving requests from the interface. These modules are responsible for translating values from the handle's .private into (say) an LDAP modification list. The module is expected to 'know' things like it's own domain SID, domain name, and any other state attached to the SAM. Simpler modules may call back to some helper routine. SAM Modules Special Module: sam_passdb In order for there to be a smooth transition, kai is writing a module that reads existing passdb backends, and translates them into SAM replies. (Also pulling data from the account policy DB etc). We also intend to write a module that does the reverse - gives the SAM a passdb interface. sam_ads This is the first of the SAM modules to be committed to the tree - mainly because I needed to coordinate work with metze (who authored most of it). This module aims to use Samba's libads code to provide an Active Directory LDAP client, suitable for use on a mixed-mode DC. While it is currently being tested against Win2k servers (with a password in the smb.conf file) it is expected to eventually use a (possibly modified) OpenLDAP server. We hope that this will assist in the construction of an Samba AD DC. We also intend to construct a Samba 2.2/3.0 compatible ldap module, again using libads code. Memory Management The 'new SAM' development effort also concerned itself with getting a sane implementation of memory management. It was decided that we would be (as much as possible) talloc based, using an 'internal talloc context' on many objects. That is, the creation of an object would initiate it's own internal talloc context, and this would be used for all operations on that object. Much of this is already implemented in passdb. Also, like passdb, it will be possible to specify that some object actually be created on a specified context. Memory management is important here because the APIs in the 'new SAM' do not use 'pdb_init()' or an equivalent. They always allocate new objects. Enumeration's are slightly different, and occur on a supplied context that 'owns' the entire list, rather than per-element. (the enumeration functions return an array of all elements - not full handles just basic (and public) info) Likewise for things that fill in a char **. For example: NTSTATUS sam_lookup_sid(const SAM_CONTEXT *context, const NT_USER_TOKEN *access_token, TALLOC_CTX *mem_ctx, const DOM_SID *sid, char **name, uint32 *type) Takes a context to allocate the 'name' on, while: NTSTATUS sam_get_account_by_sid(const SAM_CONTEXT *context, const NT_USER_TOKEN *access_token, uint32 access_desired, const DOM_SID *accountsid, SAM_ACCOUNT_HANDLE **account) Allocates a handle and stores the allocation context on that handle. I think that the following: NTSTATUS sam_enum_accounts(const SAM_CONTEXT *context, const NT_USER_TOKEN *access_token, const DOM_SID *domainsid, uint16 acct_ctrl, int32 *account_count, SAM_ACCOUNT_ENUM **accounts) Testing Testing is vital in any piece of software, and Samba is certainly no exception. In designing this new subsystem, we have taken care to ensure it is easily tested, independent of outside protocols. To this end, Jelmer has constructed 'samtest'. This utility (see torture/samtest.c) is structured like rpcclient, but instead operates on the SAM subsystem. It creates a 'custom' SAM context, that may be distinct from the default values used by the rest of the system, and can load a separate configuration file. A small number of commands are currently implemented, but these have already proved vital in testing. I expect SAM module authors will find it particularly valuable. Example useage: $ bin/samtest > context ads:ldap://192.168.1.96 (this loads a new context, using the new ADS module. The parameter is the 'location' of the ldap server) > lookup_name DOMAIN abartlet (returns a sid). Because the 'new SAM' is NT ACL based, there will be a command to specify an arbitrary NT ACL, but for now it uses 'system' by default.