Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 13 additions & 4 deletions internal/cli/client.go
Original file line numberDiff line numberDiff line change
Expand Up@@ -283,7 +283,12 @@ func runClientCreate(ctx context.Context, p *ui.Printer, pr prompter, opts clien
// installer reconciles the existing release rather than expecting a fresh
// credential (#838).
if werr := writeClientCredential(opts.credentialFile, []string{
"TRACEBLOC_CLIENT_ID=" + strconv.Itoa(pc.ID),
// TRACEBLOC_CLIENT_ID is the *auth username* the client pod sends to
// api-token-auth (cred → helm clientId → secret CLIENT_ID →
// controller getenv("CLIENT_ID") as username). The backend
// authenticates an EdgeDevice by its UUID username, NOT the numeric
// dashboard id — so write pc.Username, not pc.ID (id is display-only).
"TRACEBLOC_CLIENT_ID=" + pc.Username,
"TB_NAMESPACE=" + pc.Namespace,
"TRACEBLOC_CLIENT_ADOPTED=1",
}); werr != nil {
Expand All@@ -304,7 +309,9 @@ func runClientCreate(ctx context.Context, p *ui.Printer, pr prompter, opts clien
p.Successf("Provisioned client %q (namespace %s).", pc.Name, pc.Namespace)
if opts.credentialFile != "" {
if werr := writeClientCredential(opts.credentialFile, []string{
"TRACEBLOC_CLIENT_ID=" + strconv.Itoa(pc.ID),
// The auth username (UUID), NOT the numeric dashboard id — see the
// adopt path above. api-token-auth authenticates by username.
"TRACEBLOC_CLIENT_ID=" + pc.Username,
"TRACEBLOC_CLIENT_PASSWORD=" + password,
"TB_NAMESPACE=" + pc.Namespace,
}); werr != nil {
Expand All@@ -317,8 +324,10 @@ func runClientCreate(ctx context.Context, p *ui.Printer, pr prompter, opts clien
// Print the credential FIRST — it's the only copy (the backend stores only
// the hash), so a later config-save failure must never cost it.
p.Section("Machine credential — needed by the installer to connect this client")
p.Field("client id", strconv.Itoa(pc.ID))
p.Field("username", pc.Username)
// The installer's "Client ID" prompt takes the auth username (UUID);
// that IS TRACEBLOC_CLIENT_ID; the numeric id is a dashboard reference only.
p.Field("client id", pc.Username)
p.Field("dashboard id", strconv.Itoa(pc.ID)) // human reference at ai.tracebloc.io/clients — NOT an installer input
p.Field("password", password)
}
ilog.Logf("minted client id=%d namespace=%s", pc.ID, pc.Namespace)
Expand Down
24 changes: 18 additions & 6 deletions internal/cli/client_test.go
Original file line numberDiff line numberDiff line change
Expand Up@@ -279,6 +279,12 @@ func TestClientCreate_AnchorMint(t *testing.T) {
if !strings.Contains(out.String(), "Machine credential") {
t.Errorf("mint should print the credential, got:\n%s", out.String())
}
// The printed "client id" is what the installer's Client ID prompt consumes —
// it must be the UUID username (u-5), the same value written to the credential
// file, not the numeric dashboard id. Assert the username is shown as the id.
if !strings.Contains(out.String(), "client id") || !strings.Contains(out.String(), "u-5") {
t.Errorf("mint should print the username (u-5) as the client id, got:\n%s", out.String())
}
}

func TestClientCreate_AdoptIdempotent(t *testing.T) {
Expand DownExpand Up@@ -359,8 +365,12 @@ func TestClientCreate_CredentialFileMint(t *testing.T) {
t.Errorf("credential file mode = %o, want 600", perm)
}
kv := parseEnvFile(t, credPath)
if kv["TRACEBLOC_CLIENT_ID"] != "5" || kv["TB_NAMESPACE"] != "my-ns" || kv["TRACEBLOC_CLIENT_PASSWORD"] == "" {
t.Errorf("credential file = %v (want id=5, ns=my-ns, non-empty password)", kv)
// TRACEBLOC_CLIENT_ID must be the UUID *username* (here "u-5"), NOT the numeric
// dashboard id (5): it becomes the pod's CLIENT_ID, which controller.py sends to
// api-token-auth as the login username. The backend authenticates an EdgeDevice
// by its username, so writing the id crash-loops the client on "Unable to log in".
if kv["TRACEBLOC_CLIENT_ID"] != "u-5" || kv["TB_NAMESPACE"] != "my-ns" || kv["TRACEBLOC_CLIENT_PASSWORD"] == "" {
t.Errorf("credential file = %v (want id=u-5 [the username, not id 5], ns=my-ns, non-empty password)", kv)
}
// never-show, the real invariant: the minted password VALUE must not appear
// in stdout under any label (the string checks above are just a proxy).
Expand DownExpand Up@@ -448,10 +458,12 @@ func TestClientCreate_CredentialFileAdopt(t *testing.T) {
t.Fatalf("adopt: %v", err)
}
kv := parseEnvFile(t, credPath)
// adopt emits id + namespace + the ADOPTED marker, but NO password (the
// existing one stands; it's write-only on the backend).
if kv["TRACEBLOC_CLIENT_ID"] != "8" || kv["TB_NAMESPACE"] != "ex-ns" || kv["TRACEBLOC_CLIENT_ADOPTED"] != "1" {
t.Errorf("adopt credential file = %v (want id=8, ns=ex-ns, ADOPTED=1)", kv)
// adopt emits the username + namespace + the ADOPTED marker, but NO password
// (the existing one stands; it's write-only on the backend). Same invariant as
// the mint path: TRACEBLOC_CLIENT_ID is the UUID username ("u-8"), not id 8 —
// it's the login username the adopted client reconnects with.
if kv["TRACEBLOC_CLIENT_ID"] != "u-8" || kv["TB_NAMESPACE"] != "ex-ns" || kv["TRACEBLOC_CLIENT_ADOPTED"] != "1" {
t.Errorf("adopt credential file = %v (want id=u-8 [the username, not id 8], ns=ex-ns, ADOPTED=1)", kv)
}
if _, hasPw := kv["TRACEBLOC_CLIENT_PASSWORD"]; hasPw {
t.Errorf("adopt must not write a password (none issued), got:\n%v", kv)
Expand Down
Loading