fix(dex): store raw bcrypt hash in Password CR, not base64-encoded
The original NOTE claimed Dex's kubernetes storage types Hash as []byte, requiring the bcrypt string to be base64-encoded before storage. This was wrong: Dex v2.41 stores and compares the hash field as a plain string. The base64-encoding caused every invite login to fail with 'Invalid credentials' because Dex passed the base64 bytes (starting with 'J' not '$') directly to bcrypt. Static passwords in the configmap always used raw bcrypt strings and worked fine — confirming the dynamic CR encoding was the bug.
This commit is contained in:
@@ -17,7 +17,6 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/base64"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -98,11 +97,12 @@ func newClient(server, token string, hc *http.Client) *PasswordClient {
|
|||||||
return &PasswordClient{server: server, token: token, http: hc}
|
return &PasswordClient{server: server, token: token, http: hc}
|
||||||
}
|
}
|
||||||
|
|
||||||
// password is the wire form of a Dex Password CR. NOTE: Dex's kubernetes storage
|
// password is the wire form of a Dex Password CR. The hash field is a plain
|
||||||
// types the hash as []byte, which Kubernetes JSON-marshals as base64. So the
|
// bcrypt string (e.g. "$2a$12$..."). Dex v2.41+ stores and compares it as-is —
|
||||||
// `hash` field must carry the base64 encoding of the bcrypt string, NOT the raw
|
// it does NOT base64-decode the field. Earlier code base64-encoded the hash
|
||||||
// bcrypt string — store the raw string and Dex's base64-decode on login yields
|
// based on a misread of Dex's internal []byte type; that caused every dynamic
|
||||||
// garbage and every login fails. CreatePassword does that encoding.
|
// invite login to fail with "Invalid credentials" while static passwords (set as
|
||||||
|
// plain strings in the configmap) worked fine.
|
||||||
type password struct {
|
type password struct {
|
||||||
APIVersion string `json:"apiVersion"`
|
APIVersion string `json:"apiVersion"`
|
||||||
Kind string `json:"kind"`
|
Kind string `json:"kind"`
|
||||||
@@ -123,8 +123,7 @@ func (c *PasswordClient) CreatePassword(ctx context.Context, email, bcryptHash,
|
|||||||
Kind: "Password",
|
Kind: "Password",
|
||||||
Metadata: map[string]string{"name": passwordName(email), "namespace": "auth"},
|
Metadata: map[string]string{"name": passwordName(email), "namespace": "auth"},
|
||||||
Email: email,
|
Email: email,
|
||||||
// base64 of the bcrypt string — see the password type's NOTE.
|
Hash: bcryptHash, // raw bcrypt string — Dex compares it directly
|
||||||
Hash: base64.StdEncoding.EncodeToString([]byte(bcryptHash)),
|
|
||||||
Username: email,
|
Username: email,
|
||||||
UserID: userID,
|
UserID: userID,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package dex
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/base64"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
@@ -46,10 +45,8 @@ func TestCreatePasswordSuccess(t *testing.T) {
|
|||||||
require.Equal(t, "new-dot-user-at-example-dot-com", gotBody.Metadata["name"])
|
require.Equal(t, "new-dot-user-at-example-dot-com", gotBody.Metadata["name"])
|
||||||
require.Equal(t, "auth", gotBody.Metadata["namespace"])
|
require.Equal(t, "auth", gotBody.Metadata["namespace"])
|
||||||
|
|
||||||
// The hash is the BASE64 of the bcrypt string (Dex stores hash as []byte).
|
// Hash is stored as the raw bcrypt string — Dex compares it directly.
|
||||||
decoded, err := base64.StdEncoding.DecodeString(gotBody.Hash)
|
require.Equal(t, "$2a$12$abcdefghijklmnopqrstuv", gotBody.Hash)
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, "$2a$12$abcdefghijklmnopqrstuv", string(decoded))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCreatePasswordConflict(t *testing.T) {
|
func TestCreatePasswordConflict(t *testing.T) {
|
||||||
|
|||||||
Reference in New Issue
Block a user