Skip to content

Commit c6e13a9

Browse files
Move more API key read paths to centralized query.
Depends on #11448
1 parent 4dce403 commit c6e13a9

File tree

2 files changed

+150
-86
lines changed

2 files changed

+150
-86
lines changed

enterprise/server/backends/authdb/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ go_library(
2929
"//third_party/singleflight",
3030
"@com_github_jonboulle_clockwork//:clockwork",
3131
"@com_github_prometheus_client_golang//prometheus",
32+
"@io_gorm_gorm//:gorm",
3233
"@io_gorm_gorm//clause",
3334
"@org_golang_x_crypto//chacha20",
3435
],

enterprise/server/backends/authdb/authdb.go

Lines changed: 149 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import (
3030
"github.com/jonboulle/clockwork"
3131
"github.com/prometheus/client_golang/prometheus"
3232
"golang.org/x/crypto/chacha20"
33+
"gorm.io/gorm"
3334
"gorm.io/gorm/clause"
3435

3536
cappb "github.com/buildbuddy-io/buildbuddy/proto/capability"
@@ -199,6 +200,8 @@ func (d *AuthDB) backfillUnencryptedKeys() error {
199200
return nil
200201
}
201202

203+
// apiKeyGroup is a compact representation of a valid API key and selected
204+
// group metadata, suitable for caching.
202205
type apiKeyGroup struct {
203206
APIKeyID string
204207
UserID string
@@ -258,6 +261,36 @@ func (g *apiKeyGroup) GetGroupStatus() grpb.Group_GroupStatus {
258261
return grpb.Group_GroupStatus(g.Status)
259262
}
260263

264+
// apiKeyGroupFullData contains full API key data and data for the owning group.
265+
type apiKeyGroupFullData struct {
266+
tables.APIKey
267+
268+
UseGroupOwnedExecutors bool
269+
CacheEncryptionEnabled bool
270+
EnforceIPRules bool
271+
IsParent bool
272+
GroupStatus int32 `gorm:"column:group_status"`
273+
}
274+
275+
func (r *apiKeyGroupFullData) toAPIKeyGroup() *apiKeyGroup {
276+
return &apiKeyGroup{
277+
APIKeyID: r.APIKeyID,
278+
UserID: r.UserID,
279+
GroupID: r.GroupID,
280+
IsParent: r.IsParent,
281+
Capabilities: r.Capabilities,
282+
UseGroupOwnedExecutors: r.UseGroupOwnedExecutors,
283+
CacheEncryptionEnabled: r.CacheEncryptionEnabled,
284+
EnforceIPRules: r.EnforceIPRules,
285+
Status: r.GroupStatus,
286+
}
287+
}
288+
289+
func (r *apiKeyGroupFullData) toAPIKey() *tables.APIKey {
290+
key := r.APIKey
291+
return &key
292+
}
293+
261294
func (d *AuthDB) InsertOrUpdateUserSession(ctx context.Context, sessionID string, session *tables.Session) error {
262295
session.SessionID = sessionID
263296
// Note: this could be one query, but it's likely too complicated to be worth
@@ -408,22 +441,7 @@ func (d *AuthDB) GetAPIKeyGroupFromAPIKey(ctx context.Context, apiKey string) (i
408441
}
409442

410443
akg, _, err := d.apiKeyFetchGroup.Do(ctx, cacheKey, func(ctx context.Context) (*apiKeyGroup, error) {
411-
akg, err := d.lookupAPIKeyGroup(ctx, "authdb_get_api_key_group_by_key", sd, func(qb *query_builder.Query) error {
412-
keyClauses := query_builder.OrClauses{}
413-
if !*encryptOldKeys {
414-
keyClauses.AddOr("ak.value = ?", apiKey)
415-
}
416-
if d.apiKeyEncryptionKey != nil {
417-
encryptedAPIKey, err := d.encryptAPIKey(apiKey)
418-
if err != nil {
419-
return err
420-
}
421-
keyClauses.AddOr("ak.encrypted_value = ?", encryptedAPIKey)
422-
}
423-
keyQuery, keyArgs := keyClauses.Build()
424-
qb.AddWhereClause(keyQuery, keyArgs...)
425-
return nil
426-
})
444+
akg, err := d.lookupAPIKeyGroupByValue(ctx, sd, apiKey)
427445
if err != nil {
428446
if db.IsRecordNotFound(err) {
429447
if d.apiKeyGroupCache != nil {
@@ -454,10 +472,7 @@ func (d *AuthDB) GetAPIKeyGroupFromAPIKeyID(ctx context.Context, apiKeyID string
454472
return d, nil
455473
}
456474
}
457-
akg, err := d.lookupAPIKeyGroup(ctx, "authdb_get_api_key_group_by_id", sd, func(qb *query_builder.Query) error {
458-
qb.AddWhereClause(`ak.api_key_id = ?`, apiKeyID)
459-
return nil
460-
})
475+
akg, err := d.lookupAPIKeyGroupByID(ctx, sd, apiKeyID)
461476
if err != nil {
462477
if db.IsRecordNotFound(err) {
463478
return nil, status.UnauthenticatedErrorf("Invalid API key ID %q", redactInvalidAPIKey(apiKeyID))
@@ -471,41 +486,86 @@ func (d *AuthDB) GetAPIKeyGroupFromAPIKeyID(ctx context.Context, apiKeyID string
471486
}
472487

473488
func (d *AuthDB) lookupAPIKeyGroup(ctx context.Context, queryName, subDomain string, addConds func(*query_builder.Query) error) (*apiKeyGroup, error) {
474-
qb := d.newAPIKeyGroupQuery(subDomain)
475-
if err := addConds(qb); err != nil {
476-
return nil, err
477-
}
478-
q, args := qb.Build()
479-
akg := &apiKeyGroup{}
480-
err := d.h.NewQueryWithOpts(
481-
ctx,
482-
queryName,
483-
db.Opts().WithStaleReads(),
484-
).Raw(
485-
q,
486-
args...,
487-
).Take(akg)
489+
row, err := d.fetchAPIKey(ctx, queryName, subDomain, db.Opts().WithStaleReads(), addConds)
488490
if err != nil {
489491
return nil, err
490492
}
493+
akg := row.toAPIKeyGroup()
491494
if err := d.fillChildGroupIDs(ctx, akg); err != nil {
492495
return nil, err
493496
}
494497
return akg, nil
495498
}
496499

497-
func (d *AuthDB) newAPIKeyGroupQuery(subDomain string) *query_builder.Query {
500+
func (d *AuthDB) lookupAPIKeyGroupByValue(ctx context.Context, subDomain string, apiKey string) (*apiKeyGroup, error) {
501+
return d.lookupAPIKeyGroup(ctx, "authdb_get_api_key_group_by_key", subDomain, func(qb *query_builder.Query) error {
502+
keyClauses := query_builder.OrClauses{}
503+
if !*encryptOldKeys {
504+
keyClauses.AddOr("ak.value = ?", apiKey)
505+
}
506+
if d.apiKeyEncryptionKey != nil {
507+
encryptedAPIKey, err := d.encryptAPIKey(apiKey)
508+
if err != nil {
509+
return err
510+
}
511+
keyClauses.AddOr("ak.encrypted_value = ?", encryptedAPIKey)
512+
}
513+
keyQuery, keyArgs := keyClauses.Build()
514+
qb.AddWhereClause(keyQuery, keyArgs...)
515+
return nil
516+
})
517+
}
518+
519+
func (d *AuthDB) lookupAPIKeyGroupByID(ctx context.Context, subDomain string, apiKeyID string) (*apiKeyGroup, error) {
520+
return d.lookupAPIKeyGroup(ctx, "authdb_get_api_key_group_by_id", subDomain, func(qb *query_builder.Query) error {
521+
qb.AddWhereClause(`ak.api_key_id = ?`, apiKeyID)
522+
return nil
523+
})
524+
}
525+
526+
// fetchAPIKey retrieves complete data for a single valid API key.
527+
// The returned data contains both APIKey fields and Group fields for the
528+
// owning group.
529+
// The caller must use the addConds func to add restrictions to limit the
530+
// matching API keys to a single API key (i.e. by API key value or ID).
531+
func (d *AuthDB) fetchAPIKey(ctx context.Context, queryName, subDomain string, dbOpts interfaces.DBOptions, addConds func(*query_builder.Query) error) (*apiKeyGroupFullData, error) {
532+
rows, err := d.fetchAPIKeys(ctx, queryName, subDomain, dbOpts, addConds)
533+
if err != nil {
534+
return nil, err
535+
}
536+
if len(rows) == 0 {
537+
return nil, gorm.ErrRecordNotFound
538+
}
539+
if len(rows) > 1 {
540+
return nil, status.InternalErrorf("multiple rows found for query %q", queryName)
541+
}
542+
return rows[0], nil
543+
}
544+
545+
// fetchAPIKeys retrieves complete data for valid API keys.
546+
// The returned data contains both APIKey fields and Group fields for the
547+
// owning group.
548+
// The caller must use the addConds func to add restrictions to limit the
549+
// matching API keys to a single group or to a single API key.
550+
func (d *AuthDB) fetchAPIKeys(ctx context.Context, queryName, subDomain string, dbOpts interfaces.DBOptions, addConds func(*query_builder.Query) error) ([]*apiKeyGroupFullData, error) {
551+
qb := d.newAPIKeyLookupQuery(subDomain)
552+
if err := addConds(qb); err != nil {
553+
return nil, err
554+
}
555+
q, args := qb.Build()
556+
rq := d.h.NewQueryWithOpts(ctx, queryName, dbOpts).Raw(q, args...)
557+
return db.ScanAll(rq, &apiKeyGroupFullData{})
558+
}
559+
560+
func (d *AuthDB) newAPIKeyLookupQuery(subDomain string) *query_builder.Query {
498561
qb := query_builder.NewQuery(`
499562
SELECT
500-
ak.capabilities,
501-
ak.api_key_id,
502-
ak.user_id,
503-
g.group_id,
563+
ak.*,
504564
g.use_group_owned_executors,
505565
g.cache_encryption_enabled,
506566
g.enforce_ip_rules,
507567
g.is_parent,
508-
g.status
568+
g.status AS group_status
509569
FROM "Groups" AS g,
510570
"APIKeys" AS ak
511571
`)
@@ -773,7 +833,7 @@ func (d *AuthDB) CreateUserAPIKey(ctx context.Context, groupID, userID, label st
773833

774834
ak := tables.APIKey{
775835
UserID: userID,
776-
GroupID: u.GetGroupID(),
836+
GroupID: groupID,
777837
Label: label,
778838
Capabilities: capabilities.ToInt(caps),
779839
}
@@ -796,20 +856,21 @@ func (d *AuthDB) isGroupMember(ctx context.Context, groupID, userID string) (boo
796856
return false, nil
797857
}
798858

799-
func (d *AuthDB) getAPIKey(ctx context.Context, h interfaces.DB, apiKeyID string) (*tables.APIKey, error) {
859+
func (d *AuthDB) getAPIKey(ctx context.Context, apiKeyID string) (*tables.APIKey, error) {
800860
if apiKeyID == "" {
801861
return nil, status.InvalidArgumentError("API key ID cannot be empty.")
802862
}
803-
rq := h.NewQuery(ctx, "authdb_get_api_key_by_id").Raw(
804-
`SELECT * FROM "APIKeys" WHERE api_key_id = ? AND (expiry_usec = 0 OR expiry_usec > ?)`, apiKeyID, d.clock.Now().UnixMicro())
805-
key := &tables.APIKey{}
806-
if err := rq.Take(key); err != nil {
863+
row, err := d.fetchAPIKey(ctx, "authdb_get_api_key_by_id", "", db.Opts(), func(qb *query_builder.Query) error {
864+
qb.AddWhereClause(`ak.api_key_id = ?`, apiKeyID)
865+
return nil
866+
})
867+
if err != nil {
807868
if db.IsRecordNotFound(err) {
808869
return nil, status.NotFoundError("The requested API key was not found.")
809870
}
810871
return nil, err
811872
}
812-
return key, nil
873+
return row.toAPIKey(), nil
813874
}
814875

815876
func (d *AuthDB) GetAPIKey(ctx context.Context, apiKeyID string) (*tables.APIKey, error) {
@@ -818,7 +879,7 @@ func (d *AuthDB) GetAPIKey(ctx context.Context, apiKeyID string) (*tables.APIKey
818879
return nil, err
819880
}
820881

821-
key, err := d.getAPIKey(ctx, d.h, apiKeyID)
882+
key, err := d.getAPIKey(ctx, apiKeyID)
822883
if err != nil {
823884
return nil, err
824885
}
@@ -898,31 +959,32 @@ func (d *AuthDB) GetAPIKeys(ctx context.Context, groupID string) ([]*tables.APIK
898959
if err := authutil.AuthorizeGroupAccess(ctx, d.env, groupID); err != nil {
899960
return nil, err
900961
}
901-
q := query_builder.NewQuery(`SELECT * FROM "APIKeys"`)
902-
// Select group-owned keys only
903-
q.AddWhereClause(`user_id IS NULL OR user_id = ''`)
904-
q.AddWhereClause(`group_id = ?`, groupID)
905-
if err := authutil.AuthorizeOrgAdmin(u, groupID); err != nil {
906-
// If we're not an admin, restrict to keys that have only been made
907-
// visible to non-admins. Note: the visible_to_developers field means "visible to
908-
// non-admins" now that we have reader/writer roles.
909-
q.AddWhereClause("visible_to_developers = ?", true)
910-
}
911-
q.AddWhereClause(`impersonation = false`)
912-
q.AddWhereClause(`expiry_usec = 0 OR expiry_usec > ?`, d.clock.Now().UnixMicro())
913-
q.SetOrderBy("label", true /*ascending*/)
914-
queryStr, args := q.Build()
915-
rq := d.h.NewQuery(ctx, "authdb_get_api_keys").Raw(queryStr, args...)
916-
917-
keys := make([]*tables.APIKey, 0)
918-
err = db.ScanEach(rq, func(ctx context.Context, k *tables.APIKey) error {
919-
if err := d.fillDecryptedAPIKey(k); err != nil {
920-
return err
962+
rows, err := d.fetchAPIKeys(ctx, "authdb_get_api_keys", "", db.Opts(), func(qb *query_builder.Query) error {
963+
// Select group-owned keys only.
964+
qb.AddWhereClause(`ak.user_id IS NULL OR ak.user_id = ''`)
965+
qb.AddWhereClause(`ak.group_id = ?`, groupID)
966+
if err := authutil.AuthorizeOrgAdmin(u, groupID); err != nil {
967+
// If we're not an admin, restrict to keys that have only been made
968+
// visible to non-admins. Note: the visible_to_developers field means "visible to
969+
// non-admins" now that we have reader/writer roles.
970+
qb.AddWhereClause("ak.visible_to_developers = ?", true)
921971
}
922-
keys = append(keys, k)
972+
qb.AddWhereClause(`ak.impersonation = false`)
973+
qb.SetOrderBy("ak.label", true /*ascending*/)
923974
return nil
924975
})
925-
return keys, err
976+
if err != nil {
977+
return nil, err
978+
}
979+
keys := make([]*tables.APIKey, 0, len(rows))
980+
for _, row := range rows {
981+
k := row.toAPIKey()
982+
if err := d.fillDecryptedAPIKey(k); err != nil {
983+
return nil, err
984+
}
985+
keys = append(keys, k)
986+
}
987+
return keys, nil
926988
}
927989

928990
func (d *AuthDB) authorizeAPIKeyWrite(ctx context.Context, h interfaces.DB, apiKeyID string) (*tables.APIKey, error) {
@@ -933,7 +995,7 @@ func (d *AuthDB) authorizeAPIKeyWrite(ctx context.Context, h interfaces.DB, apiK
933995
if err != nil {
934996
return nil, err
935997
}
936-
key, err := d.getAPIKey(ctx, h, apiKeyID)
998+
key, err := d.getAPIKey(ctx, apiKeyID)
937999
if err != nil {
9381000
return nil, err
9391001
}
@@ -1052,24 +1114,25 @@ func (d *AuthDB) GetUserAPIKeys(ctx context.Context, userID, groupID string) ([]
10521114
return nil, status.PermissionDeniedError("user-owned keys are not enabled for this group")
10531115
}
10541116

1055-
q := query_builder.NewQuery(`SELECT * FROM "APIKeys"`)
1056-
q.AddWhereClause(`user_id = ?`, userID)
1057-
q.AddWhereClause(`group_id = ?`, groupID)
1058-
q.AddWhereClause(`impersonation = false`)
1059-
q.AddWhereClause(`expiry_usec = 0 OR expiry_usec > ?`, d.clock.Now().UnixMicro())
1060-
q.SetOrderBy("label", true /*=ascending*/)
1061-
queryStr, args := q.Build()
1062-
1063-
rq := d.h.NewQuery(ctx, "authdb_get_user_api_keys").Raw(queryStr, args...)
1064-
var keys []*tables.APIKey
1065-
err = db.ScanEach(rq, func(ctx context.Context, k *tables.APIKey) error {
1117+
rows, err := d.fetchAPIKeys(ctx, "authdb_get_user_api_keys", "" /*=subDomain*/, db.Opts(), func(qb *query_builder.Query) error {
1118+
qb.AddWhereClause(`ak.user_id = ?`, userID)
1119+
qb.AddWhereClause(`ak.group_id = ?`, groupID)
1120+
qb.AddWhereClause(`ak.impersonation = false`)
1121+
qb.SetOrderBy("ak.label", true /*=ascending*/)
1122+
return nil
1123+
})
1124+
if err != nil {
1125+
return nil, err
1126+
}
1127+
keys := make([]*tables.APIKey, 0, len(rows))
1128+
for _, row := range rows {
1129+
k := row.toAPIKey()
10661130
if err := d.fillDecryptedAPIKey(k); err != nil {
1067-
return err
1131+
return nil, err
10681132
}
10691133
keys = append(keys, k)
1070-
return nil
1071-
})
1072-
return keys, err
1134+
}
1135+
return keys, nil
10731136
}
10741137

10751138
func (d *AuthDB) GetUserOwnedKeysEnabled() bool {

0 commit comments

Comments
 (0)