mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
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:
@@ -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.",
|
||||
|
||||
@@ -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,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,
|
||||
|
||||
@@ -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,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.",
|
||||
|
||||
Reference in New Issue
Block a user