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:
@@ -2475,7 +2475,7 @@ func (q *querier) GetOAuth2ProviderAppByID(ctx context.Context, id uuid.UUID) (d
|
||||
return q.db.GetOAuth2ProviderAppByID(ctx, id)
|
||||
}
|
||||
|
||||
func (q *querier) GetOAuth2ProviderAppByRegistrationToken(ctx context.Context, registrationAccessToken sql.NullString) (database.OAuth2ProviderApp, error) {
|
||||
func (q *querier) GetOAuth2ProviderAppByRegistrationToken(ctx context.Context, registrationAccessToken []byte) (database.OAuth2ProviderApp, error) {
|
||||
if err := q.authorizeContext(ctx, policy.ActionRead, rbac.ResourceOauth2App); err != nil {
|
||||
return database.OAuth2ProviderApp{}, err
|
||||
}
|
||||
|
||||
@@ -3925,9 +3925,9 @@ func (s *MethodTestSuite) TestOAuth2ProviderApps() {
|
||||
}))
|
||||
s.Run("GetOAuth2ProviderAppByRegistrationToken", s.Subtest(func(db database.Store, check *expects) {
|
||||
app := dbgen.OAuth2ProviderApp(s.T(), db, database.OAuth2ProviderApp{
|
||||
RegistrationAccessToken: sql.NullString{String: "test-token", Valid: true},
|
||||
RegistrationAccessToken: []byte("test-token"),
|
||||
})
|
||||
check.Args(sql.NullString{String: "test-token", Valid: true}).Asserts(rbac.ResourceOauth2App, policy.ActionRead).Returns(app)
|
||||
check.Args([]byte("test-token")).Asserts(rbac.ResourceOauth2App, policy.ActionRead).Returns(app)
|
||||
}))
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ package dbgen
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"database/sql"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
@@ -20,6 +19,7 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
"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/db2sdk"
|
||||
"github.com/coder/coder/v2/coderd/database/dbauthz"
|
||||
@@ -161,8 +161,8 @@ func Template(t testing.TB, db database.Store, seed database.Template) database.
|
||||
|
||||
func APIKey(t testing.TB, db database.Store, seed database.APIKey, munge ...func(*database.InsertAPIKeyParams)) (key database.APIKey, token string) {
|
||||
id, _ := cryptorand.String(10)
|
||||
secret, _ := cryptorand.String(22)
|
||||
hashed := sha256.Sum256([]byte(secret))
|
||||
secret, hashed, err := apikey.GenerateSecret(22)
|
||||
require.NoError(t, err)
|
||||
|
||||
ip := seed.IPAddress
|
||||
if !ip.Valid {
|
||||
@@ -179,7 +179,7 @@ func APIKey(t testing.TB, db database.Store, seed database.APIKey, munge ...func
|
||||
ID: takeFirst(seed.ID, id),
|
||||
// 0 defaults to 86400 at the db layer
|
||||
LifetimeSeconds: takeFirst(seed.LifetimeSeconds, 0),
|
||||
HashedSecret: takeFirstSlice(seed.HashedSecret, hashed[:]),
|
||||
HashedSecret: takeFirstSlice(seed.HashedSecret, hashed),
|
||||
IPAddress: ip,
|
||||
UserID: takeFirst(seed.UserID, uuid.New()),
|
||||
LastUsed: takeFirst(seed.LastUsed, dbtime.Now()),
|
||||
@@ -194,7 +194,7 @@ func APIKey(t testing.TB, db database.Store, seed database.APIKey, munge ...func
|
||||
for _, fn := range munge {
|
||||
fn(¶ms)
|
||||
}
|
||||
key, err := db.InsertAPIKey(genCtx, params)
|
||||
key, err = db.InsertAPIKey(genCtx, params)
|
||||
require.NoError(t, err, "insert api key")
|
||||
return key, fmt.Sprintf("%s-%s", key.ID, secret)
|
||||
}
|
||||
@@ -980,16 +980,15 @@ func WorkspaceResourceMetadatums(t testing.TB, db database.Store, seed database.
|
||||
}
|
||||
|
||||
func WorkspaceProxy(t testing.TB, db database.Store, orig database.WorkspaceProxy) (database.WorkspaceProxy, string) {
|
||||
secret, err := cryptorand.HexString(64)
|
||||
secret, hashedSecret, err := apikey.GenerateSecret(64)
|
||||
require.NoError(t, err, "generate secret")
|
||||
hashedSecret := sha256.Sum256([]byte(secret))
|
||||
|
||||
proxy, err := db.InsertWorkspaceProxy(genCtx, database.InsertWorkspaceProxyParams{
|
||||
ID: takeFirst(orig.ID, uuid.New()),
|
||||
Name: takeFirst(orig.Name, testutil.GetRandomName(t)),
|
||||
DisplayName: takeFirst(orig.DisplayName, testutil.GetRandomName(t)),
|
||||
Icon: takeFirst(orig.Icon, testutil.GetRandomName(t)),
|
||||
TokenHashedSecret: hashedSecret[:],
|
||||
TokenHashedSecret: hashedSecret,
|
||||
CreatedAt: takeFirst(orig.CreatedAt, dbtime.Now()),
|
||||
UpdatedAt: takeFirst(orig.UpdatedAt, dbtime.Now()),
|
||||
DerpEnabled: takeFirst(orig.DerpEnabled, false),
|
||||
@@ -1259,7 +1258,7 @@ func OAuth2ProviderApp(t testing.TB, db database.Store, seed database.OAuth2Prov
|
||||
Jwks: seed.Jwks, // pqtype.NullRawMessage{} is not comparable, use existing value
|
||||
SoftwareID: takeFirst(seed.SoftwareID, sql.NullString{}),
|
||||
SoftwareVersion: takeFirst(seed.SoftwareVersion, sql.NullString{}),
|
||||
RegistrationAccessToken: takeFirst(seed.RegistrationAccessToken, sql.NullString{}),
|
||||
RegistrationAccessToken: seed.RegistrationAccessToken,
|
||||
RegistrationClientUri: takeFirst(seed.RegistrationClientUri, sql.NullString{}),
|
||||
})
|
||||
require.NoError(t, err, "insert oauth2 app")
|
||||
|
||||
@@ -5,7 +5,6 @@ package dbmetrics
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"slices"
|
||||
"time"
|
||||
|
||||
@@ -1104,7 +1103,7 @@ func (m queryMetricsStore) GetOAuth2ProviderAppByID(ctx context.Context, id uuid
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
func (m queryMetricsStore) GetOAuth2ProviderAppByRegistrationToken(ctx context.Context, registrationAccessToken sql.NullString) (database.OAuth2ProviderApp, error) {
|
||||
func (m queryMetricsStore) GetOAuth2ProviderAppByRegistrationToken(ctx context.Context, registrationAccessToken []byte) (database.OAuth2ProviderApp, error) {
|
||||
start := time.Now()
|
||||
r0, r1 := m.s.GetOAuth2ProviderAppByRegistrationToken(ctx, registrationAccessToken)
|
||||
m.queryLatencies.WithLabelValues("GetOAuth2ProviderAppByRegistrationToken").Observe(time.Since(start).Seconds())
|
||||
|
||||
@@ -11,7 +11,6 @@ package dbmock
|
||||
|
||||
import (
|
||||
context "context"
|
||||
sql "database/sql"
|
||||
reflect "reflect"
|
||||
time "time"
|
||||
|
||||
@@ -2325,7 +2324,7 @@ func (mr *MockStoreMockRecorder) GetOAuth2ProviderAppByID(ctx, id any) *gomock.C
|
||||
}
|
||||
|
||||
// GetOAuth2ProviderAppByRegistrationToken mocks base method.
|
||||
func (m *MockStore) GetOAuth2ProviderAppByRegistrationToken(ctx context.Context, registrationAccessToken sql.NullString) (database.OAuth2ProviderApp, error) {
|
||||
func (m *MockStore) GetOAuth2ProviderAppByRegistrationToken(ctx context.Context, registrationAccessToken []byte) (database.OAuth2ProviderApp, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetOAuth2ProviderAppByRegistrationToken", ctx, registrationAccessToken)
|
||||
ret0, _ := ret[0].(database.OAuth2ProviderApp)
|
||||
|
||||
Generated
+1
-1
@@ -1537,7 +1537,7 @@ CREATE TABLE oauth2_provider_apps (
|
||||
jwks jsonb,
|
||||
software_id text,
|
||||
software_version text,
|
||||
registration_access_token text,
|
||||
registration_access_token bytea,
|
||||
registration_client_uri text
|
||||
);
|
||||
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
ALTER TABLE oauth2_provider_apps
|
||||
ALTER COLUMN registration_access_token
|
||||
SET DATA TYPE text
|
||||
USING encode(registration_access_token, 'escape');
|
||||
@@ -0,0 +1,4 @@
|
||||
ALTER TABLE oauth2_provider_apps
|
||||
ALTER COLUMN registration_access_token
|
||||
SET DATA TYPE bytea
|
||||
USING decode(registration_access_token, 'escape');
|
||||
@@ -3956,7 +3956,7 @@ type OAuth2ProviderApp struct {
|
||||
// RFC 7591: Version of the client software
|
||||
SoftwareVersion sql.NullString `db:"software_version" json:"software_version"`
|
||||
// RFC 7592: Hashed registration access token for client management
|
||||
RegistrationAccessToken sql.NullString `db:"registration_access_token" json:"registration_access_token"`
|
||||
RegistrationAccessToken []byte `db:"registration_access_token" json:"registration_access_token"`
|
||||
// RFC 7592: URI for client configuration endpoint
|
||||
RegistrationClientUri sql.NullString `db:"registration_client_uri" json:"registration_client_uri"`
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ package database
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
@@ -246,7 +245,7 @@ type sqlcQuerier interface {
|
||||
// RFC 7591/7592 Dynamic Client Registration queries
|
||||
GetOAuth2ProviderAppByClientID(ctx context.Context, id uuid.UUID) (OAuth2ProviderApp, error)
|
||||
GetOAuth2ProviderAppByID(ctx context.Context, id uuid.UUID) (OAuth2ProviderApp, error)
|
||||
GetOAuth2ProviderAppByRegistrationToken(ctx context.Context, registrationAccessToken sql.NullString) (OAuth2ProviderApp, error)
|
||||
GetOAuth2ProviderAppByRegistrationToken(ctx context.Context, registrationAccessToken []byte) (OAuth2ProviderApp, error)
|
||||
GetOAuth2ProviderAppCodeByID(ctx context.Context, id uuid.UUID) (OAuth2ProviderAppCode, error)
|
||||
GetOAuth2ProviderAppCodeByPrefix(ctx context.Context, secretPrefix []byte) (OAuth2ProviderAppCode, error)
|
||||
GetOAuth2ProviderAppSecretByID(ctx context.Context, id uuid.UUID) (OAuth2ProviderAppSecret, error)
|
||||
|
||||
@@ -6206,7 +6206,7 @@ const getOAuth2ProviderAppByRegistrationToken = `-- name: GetOAuth2ProviderAppBy
|
||||
SELECT id, created_at, updated_at, name, icon, callback_url, redirect_uris, client_type, dynamically_registered, client_id_issued_at, client_secret_expires_at, grant_types, response_types, token_endpoint_auth_method, scope, contacts, client_uri, logo_uri, tos_uri, policy_uri, jwks_uri, jwks, software_id, software_version, registration_access_token, registration_client_uri FROM oauth2_provider_apps WHERE registration_access_token = $1
|
||||
`
|
||||
|
||||
func (q *sqlQuerier) GetOAuth2ProviderAppByRegistrationToken(ctx context.Context, registrationAccessToken sql.NullString) (OAuth2ProviderApp, error) {
|
||||
func (q *sqlQuerier) GetOAuth2ProviderAppByRegistrationToken(ctx context.Context, registrationAccessToken []byte) (OAuth2ProviderApp, error) {
|
||||
row := q.db.QueryRowContext(ctx, getOAuth2ProviderAppByRegistrationToken, registrationAccessToken)
|
||||
var i OAuth2ProviderApp
|
||||
err := row.Scan(
|
||||
@@ -6607,7 +6607,7 @@ type InsertOAuth2ProviderAppParams struct {
|
||||
Jwks pqtype.NullRawMessage `db:"jwks" json:"jwks"`
|
||||
SoftwareID sql.NullString `db:"software_id" json:"software_id"`
|
||||
SoftwareVersion sql.NullString `db:"software_version" json:"software_version"`
|
||||
RegistrationAccessToken sql.NullString `db:"registration_access_token" json:"registration_access_token"`
|
||||
RegistrationAccessToken []byte `db:"registration_access_token" json:"registration_access_token"`
|
||||
RegistrationClientUri sql.NullString `db:"registration_client_uri" json:"registration_client_uri"`
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user