The nsswitch interface is read-only – it doesn't have any add or modify operations that the name service modules could provide, and all such tools perform modifications directly to the backend.
For example, your useradd tool has been written specifically to update the local account database through /etc/passwd & /etc/shadow and nothing else. (Indeed it comes from the 'shadow' package which is solely for maintaining the passwd/shadow/group files.)
You will need a different tool for LDAP account management.¹
¹ (I don't know of any that would be still maintained; I ended up more-or-less writing my own. A shell script around ldapadd would work.)
it appears that my implementation does not have a schema that supports standard unix user fields. For example, when I dump cn=config there is no concept of a home directory or shell in the schema
Typical LDAP implementations are designed to have configurable schema; OpenLDAP ships with several traditional schema configs under /etc/(open)ldap/schema.
You're looking for the posixAccount objectClass, which – as you've already found – is in the nis schema file. However, it is an auxiliary class, and usually attached to a person or inetOrgPerson object (from the cosine schema in this case); although nothing stops you from attaching it to a device object or an applicationProcess object. (The latter is an OSI & X.500 relic but is somewhat fitting for representing e.g. a service that needs its own LDAP password and/or its own POSIX account.)
I tried creating a user with an ldif file but I get a "No such object (32)" error. I assume this is because the ou that I reference (People and Group) do not exist.
Yes, and you'll have to create the "parent" entries before you can put anything underneath. If you just started with an empty database, you'll need to create even the toplevel entry corresponding to your database suffix (e.g. dc=example,dc=org as an objectClass: domain entry, or o=My Little Org as an organization entry). Then create OUs as organizationalUnit objects – although it's not required to put users and groups under OUs; it's okay to create them under the toplevel entry; they can be moved into an OU later anyway.
(Note that you only have to add parents up to the DB suffix – you do not need to add dc=org in this example.)
Let's say you have a database entry configured like this:
dn: olcDatabase={1}mdb,cn=config objectClass: olcDatabaseConfig olcSuffix: o=Widgets Corp
This means you would need to create these entries (assuming you want the typical OU=Users kind of hierarchy):
dn: o=Widgets Corp objectClass: organization dn: ou=People,o=Widgets Corp objectClass: organizationalUnit dn: cn=Fred Foobar,ou=People,o=Widgets Corp objectClass: inetOrgPerson objectClass: posixAccount cn: Fred Foobar givenName: Fred sn: Foobar uid: fredfoo uidNumber: 1001 gidNumber: 1000 homeDirectory: /home/fredfoo [...]
Whereas if you had olcSuffix: dc=example,dc=com (AD-style), you would need to add:
dn: dc=example,dc=com objectClass: domain dn: ou=Users,dc=example,dc=com objectClass: organizationalUnit dn: uid=fredfoo,ou=Users,dc=example,dc=com objectClass: [...]
(If all programs support the "bind as app, search, bind as user" model, then whether to use dn: cn= or dn: uid= for the user entry's DN is your choice. Most programs are fine with this, as it's what Active Directory does. But if some programs only support "bind as uid=%s,ou=Users,etc according to a DN template" model, then you'll need to use uid=.)
slapaddany data directly into the backend storage. Likewise most Kerberos implementations provide akadmin.localthat lets 'root' manage accounts by default.