Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 97c3d7fdeb | |||
| 22c036d930 |
+10
-4
@@ -48,10 +48,10 @@ func (f *fakeRancher) server(t *testing.T) *httptest.Server {
|
|||||||
_ = json.NewDecoder(r.Body).Decode(&in)
|
_ = json.NewDecoder(r.Body).Decode(&in)
|
||||||
f.minted++
|
f.minted++
|
||||||
newName := in.Metadata.GenerateName + "abcd"
|
newName := in.Metadata.GenerateName + "abcd"
|
||||||
value := "token-" + newName
|
bearer := "ext/" + newName + ":secret"
|
||||||
f.byName[newName] = in.Spec.ClusterName
|
f.byName[newName] = in.Spec.ClusterName
|
||||||
f.valid[value] = newName
|
f.valid[bearer] = newName
|
||||||
out := token{Metadata: tokenMetadata{Name: newName}, Status: tokenStatus{Value: value}}
|
out := token{Metadata: tokenMetadata{Name: newName}, Status: tokenStatus{BearerToken: bearer, Value: "secret"}}
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
w.WriteHeader(http.StatusCreated)
|
w.WriteHeader(http.StatusCreated)
|
||||||
_ = json.NewEncoder(w).Encode(out)
|
_ = json.NewEncoder(w).Encode(out)
|
||||||
@@ -150,9 +150,15 @@ func TestLifecycle(t *testing.T) {
|
|||||||
t.Fatal("creds returned no secret")
|
t.Fatal("creds returned no secret")
|
||||||
}
|
}
|
||||||
tokenName, _ := creds.Data["token_name"].(string)
|
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")
|
t.Fatal("creds missing token/token_name")
|
||||||
}
|
}
|
||||||
|
// Must be the usable bearer credential (ext/<name>:<secret>), 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" {
|
if creds.Data["cluster_name"].(string) != "c-m-abc123" {
|
||||||
t.Errorf("cluster_name = %q, want c-m-abc123", creds.Data["cluster_name"])
|
t.Errorf("cluster_name = %q, want c-m-abc123", creds.Data["cluster_name"])
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,8 +59,13 @@ type tokenSpec struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type tokenStatus struct {
|
type tokenStatus struct {
|
||||||
// Value is the usable token string. It is returned only in the creation
|
// BearerToken is the full, usable credential for the Authorization header —
|
||||||
// response and never again.
|
// ext.cattle.io tokens are of the form "ext/<name>:<secret>". 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"`
|
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 {
|
if err := c.do(ctx, http.MethodPost, tokensAPIPath, body, &out); err != nil {
|
||||||
return "", "", err
|
return "", "", err
|
||||||
}
|
}
|
||||||
if out.Status.Value == "" {
|
// Prefer bearerToken ("ext/<name>:<secret>") — 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 "", "", 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.
|
// DeleteToken removes a Token by its resource name. A missing token is success.
|
||||||
|
|||||||
@@ -31,7 +31,8 @@ type tokenSpec struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type tokenStatus struct {
|
type tokenStatus struct {
|
||||||
Value string `json:"value,omitempty"`
|
BearerToken string `json:"bearerToken,omitempty"`
|
||||||
|
Value string `json:"value,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type token struct {
|
type token struct {
|
||||||
@@ -88,16 +89,19 @@ func (s *store) handle(w http.ResponseWriter, r *http.Request) {
|
|||||||
if newName == "" {
|
if newName == "" {
|
||||||
newName = req.Metadata.GenerateName + randHex(4)
|
newName = req.Metadata.GenerateName + randHex(4)
|
||||||
}
|
}
|
||||||
value := "token-" + newName + ":" + randHex(16)
|
// ext.cattle.io returns the usable credential in status.bearerToken,
|
||||||
|
// formatted "ext/<name>:<secret>"; status.value is only the secret.
|
||||||
|
secret := randHex(16)
|
||||||
|
bearer := "ext/" + newName + ":" + secret
|
||||||
t := &token{
|
t := &token{
|
||||||
APIVersion: "ext.cattle.io/v1",
|
APIVersion: "ext.cattle.io/v1",
|
||||||
Kind: "Token",
|
Kind: "Token",
|
||||||
Metadata: tokenMeta{Name: newName},
|
Metadata: tokenMeta{Name: newName},
|
||||||
Spec: req.Spec,
|
Spec: req.Spec,
|
||||||
Status: tokenStatus{Value: value},
|
Status: tokenStatus{BearerToken: bearer, Value: secret},
|
||||||
}
|
}
|
||||||
s.tokens[newName] = t
|
s.tokens[newName] = t
|
||||||
s.valid[value] = newName
|
s.valid[bearer] = newName
|
||||||
writeJSON(w, http.StatusCreated, t)
|
writeJSON(w, http.StatusCreated, t)
|
||||||
case r.Method == http.MethodGet && name != "": // read
|
case r.Method == http.MethodGet && name != "": // read
|
||||||
t, ok := s.tokens[name]
|
t, ok := s.tokens[name]
|
||||||
|
|||||||
Reference in New Issue
Block a user