# KubeSpawner and LDAPauthentication run under users LDAP UID

**URL:** <https://discourse.jupyter.org/t/kubespawner-and-ldapauthentication-run-under-users-ldap-uid/8988>\
**Category:** Zero to JupyterHub on Kubernetes\
**Tags:** help-wanted\
**Created:** [April 29, 2021, 8:42pm UTC](https://discourse.jupyter.org/t/kubespawner-and-ldapauthentication-run-under-users-ldap-uid/8988 "2021-04-29T20:42:23Z")\
**Posts on this page:** 9\
**Page:** 1

<div class="post-metadata">

**Author:** ![alemsh](https://yyz1.discourse-cdn.com/flex031/user_avatar/discourse.jupyter.org/alemsh/32/4295_2.png) [@alemsh](https://discourse.jupyter.org/u/alemsh)\
**Post date:** [April 29, 2021, 8:42pm UTC](https://discourse.jupyter.org/t/kubespawner-and-ldapauthentication-run-under-users-ldap-uid/8988/1 "2021-04-29T20:42:23Z")

</div>

Hello,

I’m using LDAP authentication to authenticate users for our Jupyterhub running in Kubernetes (using z2jh helm chart).

Currently, I’m trying to get the singleuser servers to run as the user, with the proper UID and GID provided from LDAP. I can set that up just fine using `auth_state_attributes` and can pull that in through `extraConfig` to set `NB_USER`, `NB_UID`, `NB_GID` but am still running into issues getting the singleuser server to run with those respectively.

I’ve tried following the solution provided in [Starting single-user notebook with our custom ldap docker image - #4 by manics](https://discourse.jupyter.org/t/starting-single-user-notebook-with-our-custom-ldap-docker-image/881/4) but I’m still unable to actually get the UID and GID correct.

When trying to run with the singleuser UID set to 0, I get  
`[C 2021-04-29 19:24:22.134 SingleUserNotebookApp notebookapp:2204] Running as root is not recommended. Use --allow-root to bypass.`

` extraEnv: GRANT_SUDO: "yes" NOTEBOOK_ARGS: "--allow-root"`

Doesn’t seem to resolve it. Any ideas what the issue might be? Also are there ways to do this without starting as root? Possibly with custom images?

---

<div class="post-metadata">

**Author:** ![manics](https://yyz1.discourse-cdn.com/flex031/user_avatar/discourse.jupyter.org/manics/32/85_2.png) [@manics](https://discourse.jupyter.org/u/manics)\
**Post date:** [May 1, 2021, 11:56am UTC](https://discourse.jupyter.org/t/kubespawner-and-ldapauthentication-run-under-users-ldap-uid/8988/2 "2021-05-01T11:56:09Z")

</div>

Hi! Please could you give us:

- Your full Z2JH config with secrets redacted
- Your version of Z2JH, and details of any customisations

Thanks!

---

<div class="post-metadata">

**Author:** ![alemsh](https://yyz1.discourse-cdn.com/flex031/user_avatar/discourse.jupyter.org/alemsh/32/4295_2.png) [@alemsh](https://discourse.jupyter.org/u/alemsh)\
**Post date:** [May 3, 2021, 3:01pm UTC](https://discourse.jupyter.org/t/kubespawner-and-ldapauthentication-run-under-users-ldap-uid/8988/3 "2021-05-03T15:01:21Z")

</div>

Sure thing:

Here’s our Z2JH config:

```
hub:
  config:
    Authenticator:
      enable_auth_state: true
    CryptKeeper:
      keys:
        - SECRET
    LDAPAuthenticator:
      bind_dn_template:
      - HIDDEN
      server_address: HOSTNAME
      auth_state_attributes: [uidNumber,gidNumber,uid]
      user_search_base: HIDDEN
  db:
    pvc:
      storageClassName: storage-k8s
  extraConfig:
    configClass: |
      c.JupyterHub.authenticator_class = LDAPAuthenticatorExtend
    extendedLDAP: |
      from tornado import gen
      from ldapauthenticator import LDAPAuthenticator
      class LDAPAuthenticatorExtend(LDAPAuthenticator):
        @gen.coroutine
        def pre_spawn_start(self, user, spawner):
          self.log.debug('running preSpawn hook')
          auth_state = yield spawner.user.get_auth_state()
          self.log.debug('pre_spawn_start auth_state:%s' % auth_state)
          spawner.environment["NB_UID"] = str(auth_state["uidNumber"][0])
          spawner.environment["NB_GID"] = str(auth_state["gidNumber"][0])
          spawner.environment["NB_USER"] = str(auth_state["uid"][0])
          c.KubeSpawner.uid = str(auth_state["uidNumber"][0])
        
    logging: |
      c.JupyterHub.log_level = 'DEBUG'
      c.KubeSpawner.debug = True
      c.LocalProcessSpawner.debug = True
ingress:
  annotations:
    acme.cert-manager.io/http01-edit-in-place: "true"
    cert-manager.io/cluster-issuer: letsencrypt-http01-staging
    kubernetes.io/ingress.class: jupyterhub-ingress
  enabled: true
  hosts:
  - HOSTNAME
  tls:
  - hosts:
    - HOSTNAME
    secretName: SECRETNAME
proxy:
  secretToken: SECRET
  service:
    type: ClusterIP
singleuser:
  defaultUrl: /lab
  uid: 0
  extraEnv:
    GRANT_SUDO: "yes"
    NOTEBOOK_ARGS: "--allow-root"
  storage:
    extraVolumeMounts:
    - mountPath: /home/{username}
      name: nfs-home-{username}-volume
    - mountPath: /data
      name: data-mount-volume
    extraVolumes:
    - name: nfs-home-{username}-volume
      nfs:
        path: /path/to/home
        server: HOSTNAME
    - name: data-mount-volume
      hostPath:
        path: /mnt/data
    type: none
  profilelist:
    - display_name: "Default environment"
      description: "2 CPUs/4GB Memory"
      kubespawner_override:
        cpu_guarantee: 2
        mem_guarantee: 4

```

Z2JH Version: jupyterhub-0.11.1

No other specific customizations.

I suppose to note, if I don’t set the UID to 0 for the singleuser server, it starts and the NB\_UID and NB\_GID will be set in the environment, but the container won’t be running under that UID. Setting the singleuser UID to 0 and the pod doesn’t start.

Thanks!

---

<div class="post-metadata">

**Author:** ![manics](https://yyz1.discourse-cdn.com/flex031/user_avatar/discourse.jupyter.org/manics/32/85_2.png) [@manics](https://discourse.jupyter.org/u/manics)\
**Post date:** [May 3, 2021, 7:18pm UTC](https://discourse.jupyter.org/t/kubespawner-and-ldapauthentication-run-under-users-ldap-uid/8988/4 "2021-05-03T19:18:11Z")

</div>

The magic to automatically switch user inside the singleuser image happens in a script:

> <https://github.com/jupyter/docker-stacks/blob/fde8f1eec3e70edf67560b5430545a22382334bf/base-notebook/start.sh#L41-L102>

Z2JH overrides the script, and [runs `jupyterhub-singleuser` instead](https://github.com/jupyterhub/zero-to-jupyterhub-k8s/blob/b804bac3ad6acf8b656f6108a5a18b95d9d22c10/jupyterhub/values.yaml#L378).

If you revert the override by setting it to empty:

```auto
singleuser:
  cmd:

```

the `start.sh` script should be automatically called to take care of switching user, and you won’t need `NOTEBOOK_ARGS: "--allow-root"` any more.

---

<div class="post-metadata">

**Author:** ![alemsh](https://yyz1.discourse-cdn.com/flex031/user_avatar/discourse.jupyter.org/alemsh/32/4295_2.png) [@alemsh](https://discourse.jupyter.org/u/alemsh)\
**Post date:** [May 5, 2021, 2:11pm UTC](https://discourse.jupyter.org/t/kubespawner-and-ldapauthentication-run-under-users-ldap-uid/8988/5 "2021-05-05T14:11:30Z")

</div>

Thanks much! That does allow the notebook to run as the specified user.

Now though, it seems that the `singleuser.defaultUrl` in the Z2JH config is no longer used.

I’ve tried a few different ways to set it, like `c.Spawner.default_url = '/lab` and setting certain environment variables, but doesn’t seem to work. Will probably have to see how that gets set into the singleuser command. Perhaps the additional flags aren’t being set?

---

<div class="post-metadata">

**Author:** ![alemsh](https://yyz1.discourse-cdn.com/flex031/user_avatar/discourse.jupyter.org/alemsh/32/4295_2.png) [@alemsh](https://discourse.jupyter.org/u/alemsh)\
**Post date:** [May 5, 2021, 5:58pm UTC](https://discourse.jupyter.org/t/kubespawner-and-ldapauthentication-run-under-users-ldap-uid/8988/6 "2021-05-05T17:58:02Z")

</div>

Seems like you need to specify `singleuser.cmd: "start-notebook.sh"`, otherwise arguments aren’t passed to the init script.

---

<div class="post-metadata">

**Author:** ![manics](https://yyz1.discourse-cdn.com/flex031/user_avatar/discourse.jupyter.org/manics/32/85_2.png) [@manics](https://discourse.jupyter.org/u/manics)\
**Post date:** [May 5, 2021, 8:02pm UTC](https://discourse.jupyter.org/t/kubespawner-and-ldapauthentication-run-under-users-ldap-uid/8988/7 "2021-05-05T20:02:25Z")

</div>

Yes, unfortunately it’s a bit complicated… If you want to use the default command from the Docker image you can unset `singleuser.cmd`, but if you want to use the default command with other Spawner parameters you have to explicitly add the default, which as you figured out is [`start-notebook.sh`](https://github.com/jupyter/docker-stacks/blob/28817a61f490d97fcad71fb2a9f72355611ba6f5/base-notebook/Dockerfile#L150).

See this issue for more details:

> <https://github.com/jupyterhub/kubespawner/issues/493>
>
> I saw that \`c.KubeSpawner.default\_url\` \[was reported to not have an impact\](http…s://github.com/jupyterhub/kubespawner/issues/149#issuecomment-793998324) and decided to investigate.
> 
> I end up concluding that configuring the command that the container starts with is very complicated. The Spawner, K8s, and the Dockerfile all have various names for similar things that all combine into a k8s containers \`args\` field where the \`command\` field apparently isn't configurable to my surprise.
> 
> \- \`cmd\`, \`args\`, \`notebook\_dir\`, \`default\_url\`, and \`get\_args(...)\` are defined in in the Spawner base class
> \- \`get\_args(...)\` combines \`notebook\_dir\`, \`default\_url\`, and \`args\` into a single appendix to \`cmd\`
> \- K8s containers have \`command\` and \`args\` which are the equivalent of Dockerfiles \`entrypoint\` and \`cmd\`
> \- KubeSpawner sets the containers k8s field \`args\` to the variable \`real\_cmd\` decided below, which changed in 5ee6dc6f.
> \- Z2JH's default config of KubeSpawner's \`cmd\` is \[\`jupyterhub-singleuser\`\](https://github.com/jupyterhub/jupyterhub/blob/master/setup.py#L115).
> 
> \`\`\`diff
> async def get\_pod\_manifest(self):
> # ...
> if self.cmd:
> real\_cmd = self.cmd + self.get\_args()
> else:
> # change commit comment:
> # fix use of get\_args() (calling get\_args() alone would omit the command)
> \- real\_cmd = self.get\_args()
> + real\_cmd = None
> \`\`\`
> 
> \### Investigation conclusion
> 
> 1. I'm confused we opt to set \`args\` of the k8s container based on the traitlets \`cmd\`, \`args\`, \`notebook\_dir\`, \`default\_url\` instead of setting \`command\` to \`cmd\`, and setting \`args\` to \`default\_url\`, \`notebook\_dir\` and \`args\`.
> 2. I think the logic to silently ignore the traitlets \`args\`, \`notebook\_dir\`, \`default\_url\` when \`cmd\` is unset is problematic and should lead to a warning at least.

---

<div class="post-metadata">

**Author:** ![jcs](https://avatars.discourse-cdn.com/v4/letter/j/439d5e/32.png) [@jcs](https://discourse.jupyter.org/u/jcs)\
**Post date:** [December 20, 2022, 11:26am UTC](https://discourse.jupyter.org/t/kubespawner-and-ldapauthentication-run-under-users-ldap-uid/8988/8 "2022-12-20T11:26:23Z")

</div>

Old thread, but hoping @manics you can shed some light in my z2jh deployment if you get a chance.

I have managed with your examples to get NB\_UID etc resolving to the right user attributes inside the pod, when I set “cmd:” as blank with uid=0, I get a permissions issue when the default script tries to run as it seems to be trying to write stuff to the NFS folder (/home//.local) before switching to running as the user (My NFS user folders are tied down with 700 permissions.).

I copied your start.sh above to my container, and called it from the cmd: option, and the logs show it ran ok (no errors at least), the container completes successfully (according to k8s) but the pod doesn’t stay running.

So what’s running when I leave cmd: as blank? And how do I get the pod to stay running if I call your start.sh using cmd: option?

thanks

---

<div class="post-metadata">

**Author:** ![1\_1](https://yyz1.discourse-cdn.com/flex031/user_avatar/discourse.jupyter.org/1_1/32/9980_2.png) [@1\_1](https://discourse.jupyter.org/u/1_1)\
**Post date:** [May 31, 2023, 8:32pm UTC](https://discourse.jupyter.org/t/kubespawner-and-ldapauthentication-run-under-users-ldap-uid/8988/9 "2023-05-31T20:32:51Z")

</div>

I’m looking to implement exactly this though we haven’t been able to get this working. Is someone using this now?
