Skip to content

feat: Add UEFI credential REST endpoint#3241

Open
kfelternv wants to merge 4 commits into
NVIDIA:mainfrom
kfelternv:issue-2803-uefi-credential-rest
Open

feat: Add UEFI credential REST endpoint#3241
kfelternv wants to merge 4 commits into
NVIDIA:mainfrom
kfelternv:issue-2803-uefi-credential-rest

Conversation

@kfelternv

@kfelternv kfelternv commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Resolves #2803

What this PR does

Adds a Provider Admin REST and nicocli path for creating site-default UEFI credentials without using nico-admin-cli.

  • adds POST /v2/org/{org}/nico/credential/uefi
  • accepts either the site-default Host or DPU credential kind
  • validates the site ID, credential kind, and password before forwarding the request
  • uses the existing Core credential creation proxy path
  • returns the accepted site ID and credential kind without returning the password
  • updates the OpenAPI specification, generated standard SDK, route coverage, and nicocli command surface

Why

Core already supports site-default Host and DPU UEFI credentials, but REST clients and nicocli have no equivalent operation. Operators therefore still need the admin CLI for this site-setup step.

How we verified it

Hands-on revision: 2c41f241af2af60d7101b4aa0fad94c431fe5fdd

Current head: 727eb8686829c727cd0550d36b824e4366ec82b3. The follow-up commit only adds the repository-required SPDX headers to the three generated SDK files; it does not change runtime behavior.

Environment: Linux x86_64 development VM, Docker 29.1.3, kind 0.27.0, and kubectl 1.30.8. The integrated REST/Core stack was running in the kind-kind context. The REST image was built from the PR revision layered onto the current DevSpace integration base 1a0d78b9454ad5fbf841f3886a0451efb4ad2e98.

The image was built with:

docker build --pull=false \
  -t localhost:5000/nico-rest-api:uefi-2c41f241-devspace-1a0d78b9 \
  -f rest-api/docker/local/Dockerfile.nico-rest-api \
  rest-api

The resulting image digest was sha256:699d38abac875ffefd3a599da0e1107789edee5ed62214eb9cb8ccc89f71eb01. It was loaded into kind, deployed as the nico-rest-api image, and the deployment rolled out successfully.

Hands-on verification then exercised the real route through REST, the site-agent Core proxy, Core's credential handler, and the local Vault store. Because the disposable stack seeds a Host UEFI credential, the original local value was retained in memory, the current Vault version was soft-deleted, and the original value was restored after the test.

Sanitized proof:

invalid_kind_status=400
success_status=201 site_id=1aa72b3c-7cfa-4ecc-86e1-dd6d85fdb3d8 kind=Host password_in_response=false
vault_write_observed=true
duplicate_status=409
vault_original_state_restored=true
rest_api_proxy_log_entries=2

The focused tests also passed:

cd rest-api
make ensure-postgres
go test ./api/pkg/api/handler -run 'UEFICredential' -count=1
go test ./api/pkg/api/model -run 'UEFICredential' -count=1
go test ./api/pkg/api -run 'APIRoutes' -count=1
go test ./cli/pkg -run 'UEFICredential' -count=1

These tests cover both Host and DPU mappings, invalid request rejection, secret omission in the response, route registration, and the generated nicocli create command.

For the generated-header follow-up, python3 scripts/check_source_headers.py, go test ./... from rest-api/sdk/standard, and git diff --check all passed.

How to reproduce the verification

Prerequisites:

Start from a clean checkout and assemble the exact tested revisions:

git clone https://github.com/NVIDIA/infra-controller.git
cd infra-controller
git fetch origin pull/3304/head:pr-3304 pull/3241/head:pr-3241
git checkout --detach 1a0d78b9454ad5fbf841f3886a0451efb4ad2e98
git merge --no-commit --no-ff 2c41f241af2af60d7101b4aa0fad94c431fe5fdd

IMAGE=localhost:5000/nico-rest-api:uefi-2c41f241-devspace-1a0d78b9
docker build --pull=false -t "$IMAGE" \
  -f rest-api/docker/local/Dockerfile.nico-rest-api rest-api
kind load docker-image --name kind "$IMAGE"
kubectl --context kind-kind -n nico-rest set image deployment/nico-rest-api "api=$IMAGE"
kubectl --context kind-kind -n nico-rest rollout status deployment/nico-rest-api --timeout=180s

In separate terminals, forward the REST API and Keycloak services:

kubectl --context kind-kind -n nico-rest port-forward --address 127.0.0.1 service/nico-rest-api 18388:8388
kubectl --context kind-kind -n nico-rest port-forward --address 127.0.0.1 service/keycloak 18082:8082

Then exercise validation, creation, persistence, and duplicate handling. Use localhost for Keycloak so the token issuer matches the local API configuration.

set -euo pipefail
mkdir -p "$HOME/Developer/_agent-tmp/uefi-rest-repro"

SITE_ID="$(kubectl --context kind-kind -n nico-rest get configmap nico-rest-site-agent-config -o jsonpath='{.data.CLUSTER_ID}')"
TOKEN="$(curl --fail --silent --show-error -X POST \
  http://localhost:18082/realms/nico-dev/protocol/openid-connect/token \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  -d client_id=nico-api \
  -d client_secret=nico-local-secret \
  -d grant_type=password \
  -d username=admin@example.com \
  -d password=adminpassword | jq -er '.access_token')"

API=http://127.0.0.1:18388/v2/org/test-org/nico/credential/uefi
VAULT_PATH=secrets/machines/all_hosts/site_default/uefi-metadata-items/auth
VAULT=(kubectl --context kind-kind -n vault exec statefulset/vault -- \
  env VAULT_ADDR=http://127.0.0.1:8200 VAULT_TOKEN=root vault)

INVALID_STATUS="$(jq -cn --arg site "$SITE_ID" \
  '{siteId: $site, kind: "invalid", password: "not-used"}' | \
  curl --silent --show-error -o "$HOME/Developer/_agent-tmp/uefi-rest-repro/invalid.json" \
  -w '%{http_code}' -X POST "$API" -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' --data-binary @-)"
test "$INVALID_STATUS" = 400

ORIGINAL="$("${VAULT[@]}" kv get -format=json "$VAULT_PATH" | jq -c '.data.data')"
restore_credential() {
  printf '%s' "$ORIGINAL" | "${VAULT[@]}" kv put "$VAULT_PATH" - >/dev/null
}
trap restore_credential EXIT
"${VAULT[@]}" kv delete "$VAULT_PATH" >/dev/null

read -rsp 'Temporary local UEFI password: ' UEFI_PASSWORD
printf '\n'
PAYLOAD="$(jq -cn --arg site "$SITE_ID" --arg password "$UEFI_PASSWORD" \
  '{siteId: $site, kind: "Host", password: $password}')"

CREATE_STATUS="$(curl --silent --show-error \
  -o "$HOME/Developer/_agent-tmp/uefi-rest-repro/create.json" -w '%{http_code}' \
  -X POST "$API" -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' --data-binary "$PAYLOAD")"
test "$CREATE_STATUS" = 201
jq -e --arg site "$SITE_ID" \
  '.siteId == $site and .kind == "Host" and (has("password") | not)' \
  "$HOME/Developer/_agent-tmp/uefi-rest-repro/create.json"
"${VAULT[@]}" kv get -format=json "$VAULT_PATH" | \
  jq -e --arg password "$UEFI_PASSWORD" \
  '.data.data.UsernamePassword.password == $password' >/dev/null

DUPLICATE_STATUS="$(curl --silent --show-error \
  -o "$HOME/Developer/_agent-tmp/uefi-rest-repro/duplicate.json" -w '%{http_code}' \
  -X POST "$API" -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' --data-binary "$PAYLOAD")"
test "$DUPLICATE_STATUS" = 409

restore_credential
trap - EXIT
unset ORIGINAL UEFI_PASSWORD TOKEN PAYLOAD

Expected results: invalid input returns 400; the first valid request returns 201 with only siteId and kind; Vault contains the submitted local test value; a duplicate request returns 409; and the cleanup restores the original local credential.

Signed-off-by: Kyle Felter <kfelter@nvidia.com>
@copy-pr-bot

copy-pr-bot Bot commented Jul 8, 2026

Copy link
Copy Markdown

Auto-sync is disabled for draft pull requests in this repository. Workflows must be run manually.

Contributors can view more details about this message here.

@coderabbitai

coderabbitai Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

Adds REST API support for creating site-default UEFI credentials for Host and DPU targets, including validation, Core credential proxying, route registration, OpenAPI schemas, CLI command generation, and handler/model tests.

Changes

UEFI credential API

Layer / File(s) Summary
Credential contracts and conversions
rest-api/api/pkg/api/model/ueficredential.go, rest-api/api/pkg/api/model/ueficredential_test.go
Defines Host and DPU request kinds, validates required fields, maps requests to Core credential types, and omits passwords from responses.
Credential creation handler
rest-api/api/pkg/api/handler/ueficredential.go, rest-api/api/pkg/api/handler/ueficredential_test.go
Adds authorization, Core gRPC proxying, 201 Created responses, and coverage for supported and invalid credential kinds.
REST route registration
rest-api/api/pkg/api/routes.go, rest-api/api/pkg/api/routes_test.go
Registers and verifies the POST /credential/uefi route.
OpenAPI and CLI exposure
rest-api/openapi/spec.yaml, rest-api/cli/pkg/commands_test.go
Adds the UEFI credential operation and schemas, and verifies the generated uefi-credential create command.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant APIClient
  participant CreateUEFICredentialHandler
  participant SiteAuthorization
  participant CoreCredentialService
  APIClient->>CreateUEFICredentialHandler: POST UEFI credential request
  CreateUEFICredentialHandler->>SiteAuthorization: authorize provider for site
  SiteAuthorization-->>CreateUEFICredentialHandler: authorization result
  CreateUEFICredentialHandler->>CoreCredentialService: create HostUefi or DpuUefi credential
  CoreCredentialService-->>CreateUEFICredentialHandler: creation result
  CreateUEFICredentialHandler-->>APIClient: 201 response without password
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 27.27% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The PR adds the UEFI REST endpoint, OpenAPI updates, nicocli coverage, and handler/model tests required by #2803.
Out of Scope Changes check ✅ Passed The changes shown are all directly related to the UEFI credential feature and its tests.
Title check ✅ Passed The title is concise and accurately summarizes the main change: adding a UEFI credential REST endpoint.
Description check ✅ Passed The description is clearly aligned with the code changes and documents the new REST, CLI, OpenAPI, and test coverage.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands.

@kfelternv kfelternv requested a review from thossain-nv July 8, 2026 02:41

Copy link
Copy Markdown
Contributor Author

@thossain-nv could you please review the API shape here? I implemented an additive, create-only POST /v2/org/{org}/nico/credential/uefi for the site-default Host or DPU credential, matching Core CreateCredential. Core currently rejects duplicates, so the endpoint returns 409 instead of using the BMC endpoint's create-or-update behavior. Does that contract look right for the REST parity goal, or would you prefer a different additive shape?

@kfelternv kfelternv marked this pull request as ready for review July 9, 2026 23:44
@kfelternv kfelternv requested a review from a team as a code owner July 9, 2026 23:44
@github-actions

github-actions Bot commented Jul 9, 2026

Copy link
Copy Markdown

🔍 Container Scan Summary

Service Total Critical High Medium Low Other
nico-flow 15 1 3 3 0 8
nico-nsm 7 0 1 5 0 1
nico-psm 15 1 3 3 0 8
nico-rest-api 17 1 4 4 0 8
nico-rest-cert-manager 14 1 3 3 0 7
nico-rest-db 15 1 3 3 0 8
nico-rest-site-agent 15 1 3 3 0 8
nico-rest-site-manager 14 1 3 3 0 7
nico-rest-workflow 15 1 3 3 0 8
TOTAL 127 8 26 30 0 63

Per-CVE detail lives in the per-service grype-* artifacts (JSON + SARIF). Severity counts only — no CVE IDs published here.

@github-actions

github-actions Bot commented Jul 9, 2026

Copy link
Copy Markdown

@github-actions

github-actions Bot commented Jul 9, 2026

Copy link
Copy Markdown

🔐 TruffleHog Secret Scan

No secrets or credentials found!

Your code has been scanned for 700+ types of secrets and credentials. All clear! 🎉

🔗 View scan details

🕐 Last updated: 2026-07-09 23:47:24 UTC | Commit: 2c41f24

@coderabbitai coderabbitai Bot left a comment

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.

🧹 Nitpick comments (1)
rest-api/cli/pkg/commands_test.go (1)

858-879: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

LGTM, with an optional dedupe nit.

Test correctly validates the command tree shape. The two manual find-by-name loops are identical in shape; a tiny generic helper (e.g. findCommand(cmds []*cli.Command, name string) *cli.Command) would remove the duplication, but this is purely cosmetic for an 8-line test.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@rest-api/cli/pkg/commands_test.go` around lines 858 - 879, Optionally
deduplicate the repeated command lookup logic in
TestNewApp_UEFICredentialCreateCommand by adding a findCommand helper that
accepts []*cli.Command and a name, returning the matching command or nil, then
use it for both the top-level UEFI credential command and its create subcommand.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@rest-api/cli/pkg/commands_test.go`:
- Around line 858-879: Optionally deduplicate the repeated command lookup logic
in TestNewApp_UEFICredentialCreateCommand by adding a findCommand helper that
accepts []*cli.Command and a name, returning the matching command or nil, then
use it for both the top-level UEFI credential command and its create subcommand.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: f3fed072-1341-46c6-b41c-38afeeebb840

📥 Commits

Reviewing files that changed from the base of the PR and between 8651f88 and 2c41f24.

⛔ Files ignored due to path filters (4)
  • rest-api/sdk/standard/api_uefi_credential.go is excluded by !rest-api/sdk/standard/api_*.go
  • rest-api/sdk/standard/client.go is excluded by !rest-api/sdk/standard/client.go
  • rest-api/sdk/standard/model_uefi_credential.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_uefi_credential_request.go is excluded by !rest-api/sdk/standard/model_*.go
📒 Files selected for processing (8)
  • rest-api/api/pkg/api/handler/ueficredential.go
  • rest-api/api/pkg/api/handler/ueficredential_test.go
  • rest-api/api/pkg/api/model/ueficredential.go
  • rest-api/api/pkg/api/model/ueficredential_test.go
  • rest-api/api/pkg/api/routes.go
  • rest-api/api/pkg/api/routes_test.go
  • rest-api/cli/pkg/commands_test.go
  • rest-api/openapi/spec.yaml

Signed-off-by: Kyle Felter <kfelter@nvidia.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add REST API support for site-default UEFI credentials (parity with BMC credential endpoint)

1 participant