chore!: ensure consistent secret token generation and hashing (#20388)

This PR uses the same sha256 hashing technique as we use for APIKeys. So
now all randomly generated secrets will be hashed with sha256 for
consistency.

This is a breaking change for the oauth tokens. Since oauth is only
allowed for dev builds and experimental, this is ok.
This commit is contained in:
Steven Masley
2025-10-23 15:38:49 -05:00
committed by GitHub
parent 906149317d
commit 13ca9ead3a
35 changed files with 169 additions and 179 deletions
+2 -4
View File
@@ -2,8 +2,6 @@ package httpmw
import (
"context"
"crypto/sha256"
"crypto/subtle"
"database/sql"
"errors"
"fmt"
@@ -20,6 +18,7 @@ import (
"golang.org/x/xerrors"
"cdr.dev/slog"
"github.com/coder/coder/v2/coderd/apikey"
"github.com/coder/coder/v2/coderd/database"
"github.com/coder/coder/v2/coderd/database/dbauthz"
"github.com/coder/coder/v2/coderd/database/dbtime"
@@ -188,8 +187,7 @@ func APIKeyFromRequest(ctx context.Context, db database.Store, sessionTokenFunc
}
// Checking to see if the secret is valid.
hashedSecret := sha256.Sum256([]byte(keySecret))
if subtle.ConstantTimeCompare(key.HashedSecret, hashedSecret[:]) != 1 {
if !apikey.ValidateHash(key.HashedSecret, keySecret) {
return nil, codersdk.Response{
Message: SignedOutErrorMessage,
Detail: "API key secret is invalid.",
+8 -7
View File
@@ -19,6 +19,7 @@ import (
"golang.org/x/exp/slices"
"golang.org/x/oauth2"
"github.com/coder/coder/v2/coderd/apikey"
"github.com/coder/coder/v2/coderd/database"
"github.com/coder/coder/v2/coderd/database/dbauthz"
"github.com/coder/coder/v2/coderd/database/dbgen"
@@ -32,10 +33,10 @@ import (
"github.com/coder/coder/v2/testutil"
)
func randomAPIKeyParts() (id string, secret string) {
func randomAPIKeyParts() (id string, secret string, hashedSecret []byte) {
id, _ = cryptorand.String(10)
secret, _ = cryptorand.String(22)
return id, secret
secret, hashedSecret, _ = apikey.GenerateSecret(22)
return id, secret, hashedSecret
}
func TestAPIKey(t *testing.T) {
@@ -171,10 +172,10 @@ func TestAPIKey(t *testing.T) {
t.Run("NotFound", func(t *testing.T) {
t.Parallel()
var (
db, _ = dbtestutil.NewDB(t)
id, secret = randomAPIKeyParts()
r = httptest.NewRequest("GET", "/", nil)
rw = httptest.NewRecorder()
db, _ = dbtestutil.NewDB(t)
id, secret, _ = randomAPIKeyParts()
r = httptest.NewRequest("GET", "/", nil)
rw = httptest.NewRecorder()
)
r.Header.Set(codersdk.SessionTokenHeader, fmt.Sprintf("%s-%s", id, secret))
+2 -6
View File
@@ -2,7 +2,6 @@ package httpmw_test
import (
"context"
"crypto/sha256"
"fmt"
"net"
"net/http"
@@ -142,10 +141,7 @@ func TestExtractUserRoles(t *testing.T) {
}
func addUser(t *testing.T, db database.Store, roles ...string) (database.User, string) {
var (
id, secret = randomAPIKeyParts()
hashed = sha256.Sum256([]byte(secret))
)
id, secret, hashed := randomAPIKeyParts()
if roles == nil {
roles = []string{}
}
@@ -169,7 +165,7 @@ func addUser(t *testing.T, db database.Store, roles ...string) (database.User, s
_, err = db.InsertAPIKey(context.Background(), database.InsertAPIKeyParams{
ID: id,
UserID: user.ID,
HashedSecret: hashed[:],
HashedSecret: hashed,
LastUsed: dbtime.Now(),
ExpiresAt: dbtime.Now().Add(time.Minute),
LoginType: database.LoginTypePassword,
+3 -7
View File
@@ -2,7 +2,6 @@ package httpmw_test
import (
"context"
"crypto/sha256"
"encoding/json"
"fmt"
"net"
@@ -31,10 +30,7 @@ func TestWorkspaceParam(t *testing.T) {
t.Parallel()
setup := func(db database.Store) (*http.Request, database.User) {
var (
id, secret = randomAPIKeyParts()
hashed = sha256.Sum256([]byte(secret))
)
id, secret, hashed := randomAPIKeyParts()
r := httptest.NewRequest("GET", "/", nil)
r.Header.Set(codersdk.SessionTokenHeader, fmt.Sprintf("%s-%s", id, secret))
@@ -44,7 +40,7 @@ func TestWorkspaceParam(t *testing.T) {
user, err := db.InsertUser(r.Context(), database.InsertUserParams{
ID: userID,
Email: "testaccount@coder.com",
HashedPassword: hashed[:],
HashedPassword: hashed,
Username: username,
CreatedAt: dbtime.Now(),
UpdatedAt: dbtime.Now(),
@@ -63,7 +59,7 @@ func TestWorkspaceParam(t *testing.T) {
_, err = db.InsertAPIKey(r.Context(), database.InsertAPIKeyParams{
ID: id,
UserID: user.ID,
HashedSecret: hashed[:],
HashedSecret: hashed,
LastUsed: dbtime.Now(),
ExpiresAt: dbtime.Now().Add(time.Minute),
LoginType: database.LoginTypePassword,
+2 -4
View File
@@ -2,8 +2,6 @@ package httpmw
import (
"context"
"crypto/sha256"
"crypto/subtle"
"database/sql"
"net/http"
"strings"
@@ -12,6 +10,7 @@ import (
"github.com/google/uuid"
"golang.org/x/xerrors"
"github.com/coder/coder/v2/coderd/apikey"
"github.com/coder/coder/v2/coderd/database"
"github.com/coder/coder/v2/coderd/database/dbauthz"
"github.com/coder/coder/v2/coderd/httpapi"
@@ -125,8 +124,7 @@ func ExtractWorkspaceProxy(opts ExtractWorkspaceProxyConfig) func(http.Handler)
}
// Do a subtle constant time comparison of the hash of the secret.
hashedSecret := sha256.Sum256([]byte(secret))
if subtle.ConstantTimeCompare(proxy.TokenHashedSecret, hashedSecret[:]) != 1 {
if !apikey.ValidateHash(proxy.TokenHashedSecret, secret) {
httpapi.Write(ctx, w, http.StatusUnauthorized, codersdk.Response{
Message: "Invalid external proxy token",
Detail: "Invalid proxy token secret.",