Skip to content

Add OpenConfig user provider - #513

Draft
rgildein wants to merge 1 commit into
mainfrom
feat/openconfig-user
Draft

Add OpenConfig user provider#513
rgildein wants to merge 1 commit into
mainfrom
feat/openconfig-user

Conversation

@rgildein

@rgildein rgildein commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Add OpenConfig provider implementation for the User resource
(internal/provider/openconfig/user.go) and a gnmi testdata file
(test/gnmi/testdata/openconfig/user.txt).

Also introduces CodeIgnoredField in internal/apistatus and updates
conditions.FromError to treat it as Ready=True with a warning message —
used when a provider successfully configures a resource but silently skips
fields that are not supported by the platform.

Nokia SRL limitation

The OpenConfig user model on Nokia SR Linux does not expose password
or ssh-public-key as writable leaves in the config container under
openconfig-system:system/aaa/authentication/users/user[username=X]/config.
Only username and role are settable. Additionally, only a single
role is accepted — passing multiple roles results in InvalidArgument
from the device.

Since spec.password is mandatory in the CRD but cannot be set via
OpenConfig on Nokia SRL, the provider ignores it rather than failing.
The user is created with username and role only, and the CR reaches
Ready=True with an IgnoredField warning in the condition message:

IgnoredField: field spec.password: password is not supported by the OpenConfig user model on SRLinux

spec.sshPublicKey (optional) and spec.roles with more than one entry
still return a terminal UnsupportedFieldError.

Juniper vJunos-Evolved comparison

The same OpenConfig path
(openconfig-system:system/aaa/authentication/users/user[username=X]/config)
was tested on a Juniper vJunos-Evolved 26.2R1.7 device. Juniper supports
password as a writable config leaf (returned as a hashed \$9\$... value
on read), making user password management possible there. However, role
is also a single string on Juniper (not an array), and ssh-public-key
is not part of the standard OpenConfig user model on Juniper either —
SSH key provisioning uses the separate gnsi-credentialz service
(out of scope for this provider).

The testdata file documents the expected gNMI state for a user created
with a single role — the supported subset on both platforms.

Testing

Tested against real devices using containerlab and live gNMI probing.

Nokia SR Linux

Device: Nokia SR Linux 26.7.1 (ghcr.io/nokia/srlinux:26.7.1) via containerlab, gNMI at 172.20.20.2:57400

What was tested:

  • Creating a user with username and a single role — accepted, Ready=True with IgnoredField warning for password
  • Setting password via config leaf — rejected by device (Options are [username, role])
  • Setting ssh-public-key via config leaf — rejected by device (Options are [username, role])
  • Setting multiple role values as an array — rejected by device (InvalidArgument)
  • Setting role as a native name (e.g. admin) — rejected with FailedPrecondition (leafref violation)
  • Role must be a valid OpenConfig AAA identity, e.g. openconfig-aaa-types:SYSTEM_ROLE_ADMIN

Verify user config:

gnmic -a 172.20.20.2:57400 -u admin -p 'NokiaSrl1!' --skip-verify \
  --encoding json_ietf get \
  --path 'openconfig-system:system/aaa/authentication/users'

Example output:

{
  "openconfig-system:system/aaa/authentication/users": {
    "user": [
      {
        "config": {
          "role": "SYSTEM_ROLE_ADMIN",
          "username": "testplan"
        },
        "state": {
          "role": "SYSTEM_ROLE_ADMIN",
          "username": "testplan"
        },
        "username": "testplan"
      }
    ]
  }
}

Note: password is absent from this output because the OpenConfig config container
for user only exposes username and role on Nokia SR Linux — password is not a
writable leaf. It only appears in state for users configured outside of OpenConfig
(e.g. via the native CLI or startup config), where the device reflects back the stored hash.
Any attempt to write password via gNMI is rejected with Options are [username, role].
Role values are enforced via a leafref and must be valid OpenConfig AAA identity strings —
native Nokia role names are rejected with FailedPrecondition.

Juniper vJunos-Evolved

Device: Juniper vJunos-Evolved 26.2R1.7 (vrnetlab/juniper_vjunosevolved:26.2R1.7-EVO) via containerlab, gNMI at 172.20.20.2:50051

What was tested:

  • Creating a user with username, password, and a single role — accepted
  • password is writable and returned hashed (\$9\$...) on read — write-only in practice
  • Setting role as an array — rejected by device (InvalidArgument)
  • Setting ssh-public-key in the user config — rejected (syntax error, expecting "@") — SSH keys require the separate gnsi-credentialz service
  • openconfig-aaa-types:SYSTEM_ROLE_ADMIN — accepted (treated as a plain string)
  • openconfig-aaa-types:SYSTEM_ROLE_OPERATOR — rejected (Must be a string of alphanumericals, dashes or underscores)
  • Native Juniper class names (operator, superuser) — accepted

Note: Juniper does not use OpenConfig AAA identity strings for roles. It expects
native Juniper login class names (superuser, operator, read-only, unauthorized,
or any custom class defined on the device). The colon in OpenConfig identity strings
(e.g. openconfig-aaa-types:SYSTEM_ROLE_OPERATOR) makes them invalid on Juniper.
openconfig-aaa-types:SYSTEM_ROLE_ADMIN only works incidentally because Juniper treats
it as a plain alphanumeric string. Role format is therefore platform-specific.

Verify user config:

gnmic -a 172.20.20.2:50051 -u admin -p 'admin@123' --insecure \
  --encoding json_ietf --type CONFIG get \
  --path 'openconfig-system:system/aaa/authentication/users'

Example output:

{
  "openconfig-system:system/aaa/authentication/users": {
    "user": [
      {
        "config": {
          "password": "\$9\$j9iPT6/tOIcApM8x7Vbmf5T69Atu",
          "role": "superuser",
          "username": "testplan"
        },
        "username": "testplan"
      }
    ]
  }
}
Manual testing
$ k describe user test-user
Name:         test-user
Namespace:    default
Labels:       networking.metal.ironcore.dev/device-name=leaf1
Annotations:  <none>
API Version:  networking.metal.ironcore.dev/v1alpha1
Kind:         User
Metadata:
  Creation Timestamp:  2026-08-21T06:26:34Z
  Finalizers:
    networking.metal.ironcore.dev/finalizer
  Generation:  1
  Owner References:
    API Version:           networking.metal.ironcore.dev/v1alpha1
    Block Owner Deletion:  true
    Kind:                  Device
    Name:                  leaf1
    UID:                   af2df3b3-1a9e-4995-af11-3c0396c3f9c4
  Resource Version:        77171
  UID:                     160b9049-38ed-42ce-9b27-accd3a103e72
Spec:
  Device Ref:
    Name:  leaf1
  Password:
    Secret Key Ref:
      Key:   password
      Name:  user-password
  Roles:
    Name:    openconfig-aaa-types:SYSTEM_ROLE_ADMIN
  Username:  test-user
Status:
  Conditions:
    Last Transition Time:  2026-08-21T06:26:34Z
    Message:               IgnoredField: field spec.password: password is not supported by the OpenConfig user model on SRLinux
    Observed Generation:   1
    Reason:                IgnoredField
    Status:                True
    Type:                  Ready
    Last Transition Time:  2026-08-21T06:26:34Z
    Message:
    Observed Generation:   1
    Reason:                NotPaused
    Status:                False
    Type:                  Paused
Events:                    <none>
$ gnmic --skip-verify --encoding JSON_IETF -a 172.20.20.2:57400 --timeout 10s -u admin -p 'NokiaSrl1!' get --path '/system/aaa/authentication/users/user'
[
  {
    "source": "172.20.20.2:57400",
    "timestamp": 1787293708267910582,
    "time": "2026-08-21T08:28:28.267910582+02:00",
    "updates": [
      {
        "Path": "system/aaa/authentication/users",
        "values": {
          "system/aaa/authentication/users": {
            "user": [
              {
                "config": {
                  "role": "SYSTEM_ROLE_ADMIN",
                  "username": "test-user"
                },
                "state": {
                  "role": "SYSTEM_ROLE_ADMIN",
                  "username": "test-user"
                },
                "username": "test-user"
              }
            ]
          }
        }
      }
    ]
  }
]

@rgildein rgildein self-assigned this Aug 20, 2026
@rgildein
rgildein marked this pull request as draft August 20, 2026 14:13
@rgildein
rgildein force-pushed the feat/openconfig-user branch from 1227441 to aa94822 Compare August 21, 2026 05:51
Implement UserProvider for the OpenConfig provider, targeting the
standard OpenConfig path:
openconfig-system:system/aaa/authentication/users/user[username=X]/config

Nokia SR Linux limitation: the OpenConfig user model on SRLinux does
not expose password or ssh-public-key as writable config leaves — only
username and role are settable, and only a single role is accepted.
The provider raises UnsupportedFieldError for spec.password,
spec.sshPublicKey, and spec.roles when more than one role is given.
Since spec.password is mandatory in the CRD, User CRs will always
reach Ready=False (terminal) on Nokia SRL via the OpenConfig provider.

Also adds a gnmi testdata file documenting the expected device state
for a user created with a single role.

Co-authored-by: Claude <claude@anthropic.com>
Signed-off-by: Robert Gildein <rgildein@users.noreply.github.com>
@rgildein
rgildein force-pushed the feat/openconfig-user branch from aa94822 to f480dcc Compare August 21, 2026 06:26
@github-actions

Copy link
Copy Markdown

Merging this branch will decrease overall coverage

Impacted Packages Coverage Δ 🤖
github.com/ironcore-dev/network-operator/internal/apistatus 89.66% (-6.64%) 👎
github.com/ironcore-dev/network-operator/internal/conditions 0.00% (ø)
github.com/ironcore-dev/network-operator/internal/controller/core 61.66% (-0.08%) 👎
github.com/ironcore-dev/network-operator/internal/provider/openconfig 7.67% (-0.31%) 👎

Coverage by file

Changed files (no unit tests)

Changed File Coverage Δ Total Covered Missed 🤖
github.com/ironcore-dev/network-operator/internal/apistatus/apistatus.go 89.66% (-6.64%) 29 (+2) 26 3 (+2) 👎
github.com/ironcore-dev/network-operator/internal/conditions/conditions.go 0.00% (ø) 67 (+2) 0 67 (+2)
github.com/ironcore-dev/network-operator/internal/controller/core/user_controller.go 56.73% (-0.08%) 171 (+2) 97 (+1) 74 (+1) 👎
github.com/ironcore-dev/network-operator/internal/provider/openconfig/user.go 0.00% (ø) 23 (+23) 0 23 (+23)

Please note that the "Total", "Covered", and "Missed" counts above refer to code statements instead of lines of code. The value in brackets refers to the test coverage of that file in the old version of the code.

@hardikdr hardikdr added the area/switch-automation Automation processes for network switch management and operations. label Aug 21, 2026
@hardikdr hardikdr added this to Roadmap Aug 21, 2026
Comment on lines +37 to +42

// CodeIgnoredField signals that one or more spec fields were silently
// ignored during realization because the provider does not support them.
// The resource is still considered successfully configured. [WrapTerminalError]
// does not promote these errors to terminal.
CodeIgnoredField

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Honestly, this status code doesn't seem to follow the same semantics as all others do. Other status codes result in an error being returned (while this status code then returns nil from a reconcile) and also set the status condition to false (while this status code keeps it as true). As such, I would rather avoid having it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/switch-automation Automation processes for network switch management and operations. size/L

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

3 participants