Skip to content

feat(authd-oidc): support use_short_usernames to strip @domain from OIDC-derived Linux usernames #1498

@yetanotheralex

Description

@yetanotheralex

Summary

For generic OIDC (authd-oidc), the Linux username is derived from the OIDC email claim today (genericprovider.GetUserInfoinfo.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, #1317, #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 (email verification via /userinfo where needed) and the dedupe-groups fix from #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 after the dedupe-groups fix (#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).

Wayve Linux Sign-In team

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions