feat(authd-oidc): support `use_short_usernames` to strip `@domain` from OIDC-derived Linux usernames
- Dominant language
- Go
- Stars
- 308
- Forks
- 41
- Avg merge
- 2d 4h
- Merged PRs (30d)
- 58
Description
## Summary
For generic OIDC (`authd-oidc`), the Linux username is derived from the OIDC `email` claim today (`genericprovider.GetUserInfo` → `info.NewUser(userClaims.Email, …)`). That yields full email-shaped usernames such as `alex.garcia@wayve.ai`, which propagate into passwd entries and homedirs like `/home/alex.garcia@wayve.ai`.
Long-running upstream discussions ([#574](https://github.com/canonical/authd/issues/574), [#1317](https://github.com/canonical/authd/issues/1317), [#508](https://github.com/canonical/authd/issues/508)) explore naming policy and broker UX, but **enterprises remain blocked today**: IdPs commonly issue email-shaped identities, and downstream tooling assumes POSIX-safe `login` names without `@`.
We propose an **opt-in** `[users]` knob in `broker.conf`:
- **`use_short_usernames`** (bool, default `false`): when `true`, strip the `@domain` suffix by cutting at the first `@`. The strip happens at the very top of `finishAuth`, **before** the owner auto-registration call (`b.cfg.registerOwner`), the allowed-users check (`b.userNameIsAllowed`), and the `b.isOwner`-driven `owner_extra_groups` merge. That ordering is important: it ensures all three name-based steps see a consistent stripped name, so the value persisted to `broker.conf` as `owner=` matches what `b.isOwner(authInfo.UserInfo.Name)` checks on subsequent logins, and so operators write short forms in `allowed_users` without surprise.
The UUID/sub claim is untouched (`authInfo.UserInfo.UUID` remains the canonical identity key for the daemon's user mapping).
## Versions / context
- `authd-oidc-brokers` on a branch carrying [#1476](https://github.com/canonical/authd/pull/1476) (email verification via `/userinfo` where needed) and the dedupe-groups fix from [#1496](https://github.com/canonical/authd/pull/1496).
## Failure modes the `@`-suffix causes today
- **Azure CLI** rejects `@` in certain `--user` / principal contexts; `$USER` interpolation breaks scripted Azure workflows.
- **URLs and tooling** that interpret `@` as URI userinfo (`scheme://user@host`) mis-parse paths or logs containing `$HOME`.
- **Paths under `/home/`** with `@` confuse editors, SSH wrappers, and legacy scripts that split fields naïvely.
- **NSS/GECOS/PAM**: operational surprises when integrators assume `$USER` matches typical Unix `[a-z_][a-z0-9_-]*` conventions.
## Concrete behaviour
| | Before | After (`use_short_usernames = true`) |
|---|---|---|
| Linux `login` / NSS name | `alex.garcia@wayve.ai` | `alex.garcia` |
| Homedir (under `home_base_dir`) | `/home/alex.garcia@wayve.ai` | `/home/alex.garcia` |
Guard: only strip when the first `@` is **not** at index 0 (so a pathological `@something` does not yield an empty username — falls through to the original name in that case).
## Proposed patch (unified diff)
Applies on top of [#1476](https://github.com/canonical/authd/pull/1476) **after** the dedupe-groups fix ([#1496](https://github.com/canonical/authd/pull/1496) style).
```
diff --git a/authd-oidc-brokers/internal/broker/broker.go b/authd-oidc-brokers/internal/broker/broker.go
--- a/authd-oidc-brokers/internal/broker/broker.go
+++ b/authd-oidc-brokers/internal/broker/broker.go
@@ -920,6 +920,24 @@ func (b *Broker) passwordAuth(ctx context.Context, session *session, secret stri
}
func (b *Broker) finishAuth(session *session, authInfo *token.AuthCachedInfo) (string, isAuthenticatedDataResponse) {
+ // Apply use_short_usernames before any name-based logic (owner auto-registration,
+ // allowed-users check, isOwner matching) so they all see a consistent stripped name.
+ // Opt-in via [users] use_short_usernames in broker.conf — default off preserves
+ // current behavior. The Home rewrite is defensive: today the generic provider passes
+ // userClaims.Home through (empty unless the IdP sends a "home" claim), so the home
+ // path under home_base_dir is computed downstream from the (now stripped) Name.
+ if b.cfg.useShortUsernames {
+ name := authInfo.UserInfo.Name
+ if i := strings.Index(name, "@"); i > 0 {
+ oldName := name
+ unixName := name[:i]
+ authInfo.UserInfo.Name = unixName
+ if authInfo.UserInfo.Home != "" && filepath.Base(authInfo.UserInfo.Home) == oldName {
+ authInfo.UserInfo.Home = filepath.Join(filepath.Dir(authInfo.UserInfo.Home), unixName)
+ }
+ }
+ }
+
if b.cfg.shouldRegisterOwner() {
if err := b.cfg.registerOwner(b.cfg.ConfigFile, authInfo.UserInfo.Name); err != nil {
// The user is not allowed if we fail to create the owner-autoregistration file.
diff --git a/authd-oidc-brokers/internal/broker/config.go b/authd-oidc-brokers/internal/broker/config.go
--- a/authd-oidc-brokers/internal/broker/config.go
+++ b/authd-oidc-brokers/internal/broker/config.go
@@ -53,6 +53,8 @@ const (
extraGroupsKey = "extra_groups"
// ownerExtraGroupsKey is the key in the config file for the extra groups to add to the owner.
ownerExtraGroupsKey = "owner_extra_groups"
+ // useShortUsernamesKey strips "@domain" from the OIDC-derived Linux username when true (optional).
+ useShortUsernamesKey = "use_short_usernames"
// allUsersKeyword is the keyword for the `allowed_users` key that allows access to all users.
allUsersKeyword = "ALL"
// ownerUserKeyword is the keyword for the `allowed_users` key that allows access to the owner.
@@ -94,6 +96,7 @@ type userConfig struct {
allowedSSHSuffixes []string
extraGroups []string
ownerExtraGroups []string
+ useShortUsernames bool
extraScopes []string
provider provider
@@ -253,6 +256,14 @@ func parseConfig(cfgContent []byte, dropInContent []any, p provider) (userConfig
cfg.populateUsersConfig(iniCfg.Section(usersSection))
+ if sec := iniCfg.Section(usersSection); sec != nil && sec.HasKey(useShortUsernamesKey) {
+ var parseErr error
+ cfg.useShortUsernames, parseErr = sec.Key(useShortUsernamesKey).Bool()
+ if parseErr != nil {
+ return userConfig{}, fmt.Errorf("error parsing '%s': %w", useShortUsernamesKey, parseErr)
+ }
+ }
+
return cfg, nil
}
```
We can open this as a draft PR if maintainers want it wired against `main` / `canonical/authd-oidc-brokers` packaging — happy to iterate on naming (`use_short_usernames` matches the vocabulary from [#574](https://github.com/canonical/authd/issues/574)).
— **Wayve Linux Sign-In team**
Contributor guide
Research direction
Start in authd-oidc-brokers/internal/broker/broker.go at finishAuth, then read config.go around parseConfig and the users configuration. Verify the option is parsed from [users] with false as the default and that username handling occurs before the name-based checks. Done means enabled OIDC users use the shortened name while the UUID/sub identity remains unchanged and invalid values produce a configuration error.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- go
- Domain
- authentication, backend
- Issue type
- Feature
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 72/100