diff --git a/backend_test.go b/backend_test.go index 812eef7..0bfb6c6 100644 --- a/backend_test.go +++ b/backend_test.go @@ -48,10 +48,10 @@ func (f *fakeRancher) server(t *testing.T) *httptest.Server { _ = json.NewDecoder(r.Body).Decode(&in) f.minted++ newName := in.Metadata.GenerateName + "abcd" - value := "token-" + newName + bearer := "ext/" + newName + ":secret" f.byName[newName] = in.Spec.ClusterName - f.valid[value] = newName - out := token{Metadata: tokenMetadata{Name: newName}, Status: tokenStatus{Value: value}} + f.valid[bearer] = newName + out := token{Metadata: tokenMetadata{Name: newName}, Status: tokenStatus{BearerToken: bearer, Value: "secret"}} w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) _ = json.NewEncoder(w).Encode(out) @@ -150,9 +150,15 @@ func TestLifecycle(t *testing.T) { t.Fatal("creds returned no secret") } tokenName, _ := creds.Data["token_name"].(string) - if tokenName == "" || creds.Data["token"].(string) == "" { + tokenVal, _ := creds.Data["token"].(string) + if tokenName == "" || tokenVal == "" { t.Fatal("creds missing token/token_name") } + // Must be the usable bearer credential (ext/:), not the bare + // secret fragment from status.value — see the bearerToken fix. + if !strings.HasPrefix(tokenVal, "ext/") { + t.Errorf("token = %q, want the ext/ bearerToken form", tokenVal) + } if creds.Data["cluster_name"].(string) != "c-m-abc123" { t.Errorf("cluster_name = %q, want c-m-abc123", creds.Data["cluster_name"]) } diff --git a/client.go b/client.go index c36bc74..aca36ce 100644 --- a/client.go +++ b/client.go @@ -59,8 +59,13 @@ type tokenSpec struct { } type tokenStatus struct { - // Value is the usable token string. It is returned only in the creation - // response and never again. + // BearerToken is the full, usable credential for the Authorization header — + // ext.cattle.io tokens are of the form "ext/:". It is returned + // only in the creation response and never again. This is the field to use; + // Value alone is just the secret fragment and does NOT authenticate. + BearerToken string `json:"bearerToken,omitempty"` + // Value is the secret fragment. Kept as a fallback for Rancher builds that + // don't populate bearerToken. Value string `json:"value,omitempty"` } @@ -124,10 +129,16 @@ func (c *rancherClient) MintToken(ctx context.Context, req mintRequest) (value, if err := c.do(ctx, http.MethodPost, tokensAPIPath, body, &out); err != nil { return "", "", err } - if out.Status.Value == "" { + // Prefer bearerToken ("ext/:") — the field that actually + // authenticates. Fall back to value only if a Rancher build omits it. + tok := out.Status.BearerToken + if tok == "" { + tok = out.Status.Value + } + if tok == "" { return "", "", errors.New("rancher returned an empty token value") } - return out.Status.Value, out.Metadata.Name, nil + return tok, out.Metadata.Name, nil } // DeleteToken removes a Token by its resource name. A missing token is success. diff --git a/test/mockrancher/main.go b/test/mockrancher/main.go index 066cc50..9ad0627 100644 --- a/test/mockrancher/main.go +++ b/test/mockrancher/main.go @@ -31,7 +31,8 @@ type tokenSpec struct { } type tokenStatus struct { - Value string `json:"value,omitempty"` + BearerToken string `json:"bearerToken,omitempty"` + Value string `json:"value,omitempty"` } type token struct { @@ -88,16 +89,19 @@ func (s *store) handle(w http.ResponseWriter, r *http.Request) { if newName == "" { newName = req.Metadata.GenerateName + randHex(4) } - value := "token-" + newName + ":" + randHex(16) + // ext.cattle.io returns the usable credential in status.bearerToken, + // formatted "ext/:"; status.value is only the secret. + secret := randHex(16) + bearer := "ext/" + newName + ":" + secret t := &token{ APIVersion: "ext.cattle.io/v1", Kind: "Token", Metadata: tokenMeta{Name: newName}, Spec: req.Spec, - Status: tokenStatus{Value: value}, + Status: tokenStatus{BearerToken: bearer, Value: secret}, } s.tokens[newName] = t - s.valid[value] = newName + s.valid[bearer] = newName writeJSON(w, http.StatusCreated, t) case r.Method == http.MethodGet && name != "": // read t, ok := s.tokens[name]