Skip to content
This repository was archived by the owner on Sep 30, 2024. It is now read-only.

Commit 8c996a7

Browse files
authored
codeintel: first implementation of auto-indexing secrets (#45580)
1 parent de5b567 commit 8c996a7

File tree

31 files changed

+663
-199
lines changed

31 files changed

+663
-199
lines changed

client/web/src/enterprise/executors/secrets/ExecutorSecretsListPage.tsx

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -154,16 +154,18 @@ const ExecutorSecretsListPage: React.FunctionComponent<React.PropsWithChildren<E
154154
)}
155155

156156
<div className="d-flex mb-3">
157-
{Object.values(ExecutorSecretScope).map(scope => (
158-
<ExecutorSecretScopeSelector
159-
key={scope}
160-
scope={scope}
161-
label={executorSecretScopeContext(scope).label}
162-
onSelect={() => setSelectedScope(scope)}
163-
selected={scope === selectedScope}
164-
description={executorSecretScopeContext(scope).description}
165-
/>
166-
))}
157+
{(namespaceID === null ? Object.values(ExecutorSecretScope) : [ExecutorSecretScope.BATCHES]).map(
158+
scope => (
159+
<ExecutorSecretScopeSelector
160+
key={scope}
161+
scope={scope}
162+
label={executorSecretScopeContext(scope).label}
163+
onSelect={() => setSelectedScope(scope)}
164+
selected={scope === selectedScope}
165+
description={executorSecretScopeContext(scope).description}
166+
/>
167+
)
168+
)}
167169
</div>
168170

169171
<Container>
@@ -207,5 +209,7 @@ function executorSecretScopeContext(scope: ExecutorSecretScope): { label: string
207209
switch (scope) {
208210
case ExecutorSecretScope.BATCHES:
209211
return { label: 'Batch changes', description: 'Batch change execution secrets' }
212+
case ExecutorSecretScope.CODEINTEL:
213+
return { label: 'Code graph', description: 'Code graph execution secrets' }
210214
}
211215
}

client/web/src/enterprise/executors/secrets/SecretAccessLogsModal.tsx

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -77,13 +77,10 @@ const ExecutorSecretAccessLogNode: React.FunctionComponent<React.PropsWithChildr
7777
<div className="d-flex justify-content-between align-items-center flex-wrap mb-0">
7878
<PersonLink
7979
person={{
80-
displayName: node.user.displayName || node.user.username,
81-
email: node.user.email,
82-
user: {
83-
displayName: node.user.displayName,
84-
url: node.user.url,
85-
username: node.user.username,
86-
},
80+
// empty strings are fine here, as they are only used when `user` is not null
81+
displayName: (node.user?.displayName || node.user?.username) ?? '',
82+
email: node.user?.email ?? '',
83+
user: node.user,
8784
}}
8885
/>
8986
<Timestamp date={node.createdAt} />

cmd/frontend/graphqlbackend/executor_secret_access_log.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,11 @@ func (r *executorSecretAccessLogResolver) User(ctx context.Context) (*UserResolv
7777
return NewUserResolver(r.db, r.preloadedUser), nil
7878
}
7979

80-
u, err := UserByIDInt32(ctx, r.db, r.log.UserID)
80+
if r.log.UserID == nil {
81+
return nil, nil
82+
}
83+
84+
u, err := UserByIDInt32(ctx, r.db, *r.log.UserID)
8185
if err != nil {
8286
if errcode.IsNotFound(err) {
8387
return nil, nil
@@ -87,6 +91,10 @@ func (r *executorSecretAccessLogResolver) User(ctx context.Context) (*UserResolv
8791
return u, nil
8892
}
8993

94+
func (r *executorSecretAccessLogResolver) MachineUser() string {
95+
return r.log.MachineUser
96+
}
97+
9098
func (r *executorSecretAccessLogResolver) CreatedAt() gqlutil.DateTime {
9199
return gqlutil.DateTime{Time: r.log.CreatedAt}
92100
}

cmd/frontend/graphqlbackend/executor_secret_access_logs_connection.go

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,10 @@ func (r *executorSecretAccessLogConnectionResolver) Nodes(ctx context.Context) (
3838
log: log,
3939
attemptPreloadedUser: true,
4040
}
41-
if user, ok := userMap[log.UserID]; ok {
42-
r.preloadedUser = user
41+
if log.UserID != nil {
42+
if user, ok := userMap[*log.UserID]; ok {
43+
r.preloadedUser = user
44+
}
4345
}
4446
resolvers = append(resolvers, r)
4547
}
@@ -75,9 +77,12 @@ func (r *executorSecretAccessLogConnectionResolver) compute(ctx context.Context)
7577
userIDMap := make(map[int32]struct{})
7678
userIDs := []int32{}
7779
for _, log := range r.logs {
78-
if _, ok := userIDMap[log.UserID]; !ok {
79-
userIDMap[log.UserID] = struct{}{}
80-
userIDs = append(userIDs, log.UserID)
80+
if log.UserID == nil {
81+
continue
82+
}
83+
if _, ok := userIDMap[*log.UserID]; !ok {
84+
userIDMap[*log.UserID] = struct{}{}
85+
userIDs = append(userIDs, *log.UserID)
8186
}
8287
}
8388
r.users, r.err = r.db.Users().List(ctx, &database.UsersListOptions{UserIDs: userIDs})

cmd/frontend/graphqlbackend/schema.graphql

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1723,6 +1723,11 @@ enum ExecutorSecretScope {
17231723
The secret is meant to be used with Batch Changes execution.
17241724
"""
17251725
BATCHES
1726+
1727+
"""
1728+
The secret is meant to be used with Auto-indexing.
1729+
"""
1730+
CODEINTEL
17261731
}
17271732

17281733
"""
@@ -1837,8 +1842,14 @@ type ExecutorSecretAccessLog implements Node {
18371842
executorSecret: ExecutorSecret!
18381843
"""
18391844
The user in which name the secret has been used.
1845+
This is null when the access was not by a user account, or
1846+
when the user account was deleted.
18401847
"""
1841-
user: User!
1848+
user: User
1849+
"""
1850+
True when the secret was accessed by an internal procedure.
1851+
"""
1852+
machineUser: String!
18421853
"""
18431854
The date and time when the secret has been used.
18441855
"""

enterprise/cmd/frontend/internal/executorqueue/queues/codeintel/queue.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import (
1414

1515
func QueueOptions(observationCtx *observation.Context, db database.DB, accessToken func() string) handler.QueueOptions[types.Index] {
1616
recordTransformer := func(ctx context.Context, _ string, record types.Index, resourceMetadata handler.ResourceMetadata) (apiclient.Job, error) {
17-
return transformRecord(record, resourceMetadata, accessToken())
17+
return transformRecord(ctx, record, db, resourceMetadata, accessToken())
1818
}
1919

2020
store := store.New(observationCtx, db.Handle(), autoindexing.IndexWorkerStoreOptions)

enterprise/cmd/frontend/internal/executorqueue/queues/codeintel/transform.go

Lines changed: 77 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,85 @@
11
package codeintel
22

33
import (
4+
"context"
45
"fmt"
56
"strconv"
67
"strings"
78

9+
"golang.org/x/exp/maps"
10+
811
"github.com/c2h5oh/datasize"
912
"github.com/kballard/go-shellquote"
1013

1114
"github.com/sourcegraph/sourcegraph/enterprise/cmd/frontend/internal/executorqueue/handler"
1215
"github.com/sourcegraph/sourcegraph/enterprise/internal/codeintel/shared/types"
1316
apiclient "github.com/sourcegraph/sourcegraph/enterprise/internal/executor"
1417
"github.com/sourcegraph/sourcegraph/internal/conf"
18+
"github.com/sourcegraph/sourcegraph/internal/database"
19+
"github.com/sourcegraph/sourcegraph/internal/encryption/keyring"
20+
)
21+
22+
const (
23+
defaultOutfile = "dump.lsif"
24+
uploadRoute = "/.executors/lsif/upload"
25+
schemeExecutorToken = "token-executor"
1526
)
1627

17-
const defaultOutfile = "dump.lsif"
18-
const uploadRoute = "/.executors/lsif/upload"
19-
const schemeExecutorToken = "token-executor"
28+
// accessLogTransformer sets the approriate fields on the executor secret access log entry
29+
// for auto-indexing access
30+
type accessLogTransformer struct {
31+
database.ExecutorSecretAccessLogCreator
32+
}
33+
34+
func (e *accessLogTransformer) Create(ctx context.Context, log *database.ExecutorSecretAccessLog) error {
35+
log.MachineUser = "codeintel-autoindexing"
36+
log.UserID = nil
37+
return e.ExecutorSecretAccessLogCreator.Create(ctx, log)
38+
}
2039

21-
func transformRecord(index types.Index, resourceMetadata handler.ResourceMetadata, accessToken string) (apiclient.Job, error) {
40+
func transformRecord(ctx context.Context, index types.Index, db database.DB, resourceMetadata handler.ResourceMetadata, accessToken string) (apiclient.Job, error) {
2241
resourceEnvironment := makeResourceEnvironment(resourceMetadata)
2342

43+
var secrets []*database.ExecutorSecret
44+
var err error
45+
if len(index.RequestedEnvVars) > 0 {
46+
secretsStore := db.ExecutorSecrets(keyring.Default().ExecutorSecretKey)
47+
secrets, _, err = secretsStore.List(ctx, database.ExecutorSecretScopeCodeIntel, database.ExecutorSecretsListOpts{
48+
// Note: No namespace set, codeintel secrets are only available in the global namespace for now.
49+
Keys: index.RequestedEnvVars,
50+
})
51+
if err != nil {
52+
return apiclient.Job{}, err
53+
}
54+
}
55+
56+
// And build the env vars from the secrets.
57+
secretEnvVars := make([]string, len(secrets))
58+
redactedEnvVars := make(map[string]string, len(secrets))
59+
secretStore := &accessLogTransformer{db.ExecutorSecretAccessLogs()}
60+
for i, secret := range secrets {
61+
// Get the secret value. This also creates an access log entry in the
62+
// name of the user.
63+
val, err := secret.Value(ctx, secretStore)
64+
if err != nil {
65+
return apiclient.Job{}, err
66+
}
67+
68+
secretEnvVars[i] = fmt.Sprintf("%s=%s", secret.Key, val)
69+
// We redact secret values as ${{ secrets.NAME }}.
70+
redactedEnvVars[val] = fmt.Sprintf("${{ secrets.%s }}", secret.Key)
71+
}
72+
73+
envVars := append(resourceEnvironment, secretEnvVars...)
74+
2475
dockerSteps := make([]apiclient.DockerStep, 0, len(index.DockerSteps)+2)
2576
for i, dockerStep := range index.DockerSteps {
2677
dockerSteps = append(dockerSteps, apiclient.DockerStep{
2778
Key: fmt.Sprintf("pre-index.%d", i),
2879
Image: dockerStep.Image,
2980
Commands: dockerStep.Commands,
3081
Dir: dockerStep.Root,
31-
Env: resourceEnvironment,
82+
Env: envVars,
3283
})
3384
}
3485

@@ -38,7 +89,7 @@ func transformRecord(index types.Index, resourceMetadata handler.ResourceMetadat
3889
Image: index.Indexer,
3990
Commands: append(index.LocalSteps, shellquote.Join(index.IndexerArgs...)),
4091
Dir: index.Root,
41-
Env: resourceEnvironment,
92+
Env: envVars,
4293
})
4394
}
4495

@@ -57,11 +108,8 @@ func transformRecord(index types.Index, resourceMetadata handler.ResourceMetadat
57108
outfile = defaultOutfile
58109
}
59110

60-
fetchTags := false
61111
// TODO: Temporary workaround. LSIF-go needs tags, but they make git fetching slower.
62-
if strings.HasPrefix(index.Indexer, "sourcegraph/lsif-go") {
63-
fetchTags = true
64-
}
112+
fetchTags := strings.HasPrefix(index.Indexer, "sourcegraph/lsif-go")
65113

66114
dockerSteps = append(dockerSteps, apiclient.DockerStep{
67115
Key: "upload",
@@ -87,29 +135,35 @@ func transformRecord(index types.Index, resourceMetadata handler.ResourceMetadat
87135
},
88136
})
89137

138+
allRedactedValues := map[string]string{
139+
// 🚨 SECURITY: Catch leak of authorization header.
140+
authorizationHeader: redactedAuthorizationHeader,
141+
142+
// 🚨 SECURITY: Catch uses of fragments pulled from auth header to
143+
// construct another target (in src-cli). We only pass the
144+
// Authorization header to src-cli, which we trust not to ship the
145+
// values to a third party, but not to trust to ensure the values
146+
// are absent from the command's stdout or stderr streams.
147+
accessToken: "PASSWORD_REMOVED",
148+
}
149+
// 🚨 SECURITY: Catch uses of executor secrets from the executor secret store
150+
maps.Copy(allRedactedValues, redactedEnvVars)
151+
90152
return apiclient.Job{
91153
ID: index.ID,
92154
Commit: index.Commit,
93155
RepositoryName: index.RepositoryName,
94156
ShallowClone: true,
95157
FetchTags: fetchTags,
96158
DockerSteps: dockerSteps,
97-
RedactedValues: map[string]string{
98-
// 🚨 SECURITY: Catch leak of authorization header.
99-
authorizationHeader: redactedAuthorizationHeader,
100-
101-
// 🚨 SECURITY: Catch uses of fragments pulled from auth header to
102-
// construct another target (in src-cli). We only pass the
103-
// Authorization header to src-cli, which we trust not to ship the
104-
// values to a third party, but not to trust to ensure the values
105-
// are absent from the command's stdout or stderr streams.
106-
accessToken: "PASSWORD_REMOVED",
107-
},
159+
RedactedValues: allRedactedValues,
108160
}, nil
109161
}
110162

111-
const defaultMemory = "12G"
112-
const defaultDiskSpace = "20G"
163+
const (
164+
defaultMemory = "12G"
165+
defaultDiskSpace = "20G"
166+
)
113167

114168
func makeResourceEnvironment(resourceMetadata handler.ResourceMetadata) []string {
115169
env := []string{}

0 commit comments

Comments
 (0)