Skip to content

A new PKI View - #262

Open
doanac wants to merge 5 commits into
mainfrom
security-view
Open

A new PKI View#262
doanac wants to merge 5 commits into
mainfrom
security-view

Conversation

@doanac

@doanac doanac commented Sep 4, 2026

Copy link
Copy Markdown
Member

As this project matures we are seeing that operators aren't going to spend time directly logged into the server and there's a need to expose more data via UIs. This introduces a small change to expose PKI related information to the user.

Andy Doan and others added 2 commits September 4, 2026 11:48
I've stumbled on this serveral times now working with certs.
It gets confusing to look in your certs directory and see root.crt,
device-ca.crt and then tls.pem. Granted we also have a cas.pem, but this
file contains multiple certs.

Signed-off-by: Andy Doan <doanac@qti.qualcomm.com>
This helps users get information to debug registration and ctonnection
issues with devices.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: Andy Doan <doanac@qti.qualcomm.com>
@doanac

doanac commented Sep 4, 2026

Copy link
Copy Markdown
Member Author

CLI Example:

$ fiocli pki show
Root CA:
  Serial:  01:45:a9:51:8c:9f:c4:a6:c3:6e:83:cd:56:c1:1c:9c:e8:bb:f7:3f
  Issuer:  CN=Factory-CA,OU=andy-corp
  Subject: CN=Factory-CA,OU=andy-corp
  Expires: 2043-05-03 14:45:51 UTC

Gateway TLS Certificate:
  Serial:  de:51:54:f1:3b:2b:64:0c:cc:71:48:0f:f4:dd:3e:de:1b:a9:fa:b4
  Issuer:  CN=Factory-CA,OU=andy-corp
  Subject: CN=doanac2-mac,OU=andy-corp
  Expires: 2036-03-25 17:38:24 UTC

Device CA:
  Serial:  4b:69:e9:37:fe:ea:7c:64:38:a7:ec:35:ff:4b:37:ff:ba:95:71:0e
  Issuer:  CN=Factory-CA,OU=andy-corp
  Subject: CN=fio-59db9c9a1c85010019e023cc,OU=andy-corp
  Expires: 2033-05-05 14:45:51 UTC

CA Bundles:
  Serial:  4b:69:e9:37:fe:ea:7c:64:38:a7:ec:35:ff:4b:37:ff:ba:95:71:0d
  Issuer:  CN=Factory-CA,OU=andy-corp
  Subject: CN=4b0182b0-9199-4b60-9bf4-70efff1180d8.ota-lite.foundries.io,OU=andy-corp
  Expires: 2033-05-05 14:45:51 UTC

  Serial:  4b:69:e9:37:fe:ea:7c:64:38:a7:ec:35:ff:4b:37:ff:ba:95:71:0e
  Issuer:  CN=Factory-CA,OU=andy-corp
  Subject: CN=fio-59db9c9a1c85010019e023cc,OU=andy-corp
  Expires: 2033-05-05 14:45:51 UTC

  Serial:  e3:6e:21:7f:e7:42:ce:e1:63:4f:3f:66:8d:11:b0:a9:d3:13:10:da
  Issuer:  CN=Factory-CA,OU=andy-corp
  Subject: CN=fio-59db9c9a1c85010019e023cc,OU=andy-corp
  Expires: 2033-12-13 21:24:29 UTC

@doanac

doanac commented Sep 4, 2026

Copy link
Copy Markdown
Member Author

Web UI

image

@doanac
doanac requested review from mike-scott and vkhoroz and removed request for mike-scott September 4, 2026 19:26
Andy Doan and others added 3 commits September 4, 2026 14:29
Signed-off-by: Andy Doan <doanac@qti.qualcomm.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: Andy Doan <doanac@qti.qualcomm.com>
Covers the "fiocli pki show" command and the "/pki" page added in
9b6e0d87, 32efa5c1, and 6e07ea0a, checking both report the same root
CA, device CA, and gateway TLS certificates.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: Andy Doan <doanac@qti.qualcomm.com>

@vkhoroz vkhoroz left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Looks good.

My comments are important yet easy to take.

Comment thread server/ui/api/handlers.go
var EchoError = server.EchoError

func RegisterHandlers(e *echo.Echo, ca *DeviceCa, storage *storage.Storage, userStorage *users.Storage, a auth.Provider) {
func RegisterHandlers(e *echo.Echo, ca *DeviceCa, storage *storage.Storage, userStorage *users.Storage, fs *storage.FsHandle, a auth.Provider) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This inclusion breaks our indirection/encapsulation layering.

Those "trivial" certificate files access operations need to be added to the API's storage.Storage, so that the API handlers don't have to access the filesystem directly.

Comment on lines +46 to +54
buf, err := h.fs.Certs.ReadFile(name)
if err != nil {
if errors.Is(err, os.ErrNotExist) {
buf = nil
} else {
return EchoError(c, err, http.StatusInternalServerError, "Failed to read certificate: "+name)
}
}
resp[name] = string(buf)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This file existence check would look good on the storage/api level to unload the handler.

// @Summary Get the CA bundle trusted for device mTLS
// @Produce text/plain
// @Success 200
// @Router /pki/cas.pem [get]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Why is this different?
I do get that cas.pem is a list set of certs rather than a single cert, but from the API perspective we're simply returning a map of PKI files, so the unification is as simple as using a plural for /pki/certs endpoint.

buf, err := h.fs.Certs.ReadFile(name)
if err != nil {
if errors.Is(err, os.ErrNotExist) {
buf = nil

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I'm not sure one can cast nil to a string at line 54.

cobra.CheckErr(printCertSummaries(certBytes))
}
}
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Once you unify the API, this can be unified too. There is technically no reason why cas.pem is treated specially here.
One tiny problem I already see with this "special" treatment is that:

  • when e.g. tls.crt does not exist, we show not configured;
  • but when cas.pem does not exist , we show a 404 error.

Comment on lines +133 to +136
fmt.Printf(" Serial: %s\n", formatSerial(cert.SerialNumber.Bytes()))
fmt.Printf(" Issuer: %s\n", cert.Issuer)
fmt.Printf(" Subject: %s\n", cert.Subject)
fmt.Printf(" Expires: %s\n", cert.NotAfter.Format("2006-01-02 15:04:05 MST"))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Maybe, use a \t instead of two spaces as an indentation?

for _, name := range opts.Names {
if !slices.Contains(allowed, name) {
msg := "Invalid certificate requested: " + name
msg := "invalid certificate requested: " + name

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

it looks like these two changes belong to a different commit.

Comment thread server/ui/web/http.go
return nil, fmt.Errorf("unexpected status code: HTTP_%d: %s", resp.StatusCode, string(body))
}
return io.ReadAll(resp.Body)
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

A small change to reqJsonWithHeaders would make it a reqWithHeaders, so that you might enclose 90% of common code between reqJsonWithHeaders and getRaw in one place.
These "small deviations" costs a lot as the product evolves, as it was with getJson/putJson/postJson before the consolidation.

Comment thread server/ui/web/http.go
if err != nil {
return nil, err
}
auth.PassCsrfCookie(ctx, req)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

IIUC CSRF cookie is only needed for "update" operations... we don't need it for the GET method.
At least getJson doesn't send it and works somehow.

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.

2 participants