@@ -2,10 +2,12 @@ package provisionerdserver
2
2
3
3
import (
4
4
"context"
5
+ "crypto/sha256"
5
6
"database/sql"
6
7
"encoding/json"
7
8
"errors"
8
9
"fmt"
10
+ "net"
9
11
"net/http"
10
12
"net/url"
11
13
"reflect"
@@ -37,6 +39,7 @@ import (
37
39
"github.com/coder/coder/coderd/telemetry"
38
40
"github.com/coder/coder/coderd/tracing"
39
41
"github.com/coder/coder/codersdk"
42
+ "github.com/coder/coder/cryptorand"
40
43
"github.com/coder/coder/provisioner"
41
44
"github.com/coder/coder/provisionerd/proto"
42
45
"github.com/coder/coder/provisionersdk"
@@ -62,6 +65,7 @@ type Server struct {
62
65
QuotaCommitter * atomic.Pointer [proto.QuotaCommitter ]
63
66
Auditor * atomic.Pointer [audit.Auditor ]
64
67
TemplateScheduleStore * atomic.Pointer [schedule.TemplateScheduleStore ]
68
+ DeploymentValues * codersdk.DeploymentValues
65
69
66
70
AcquireJobDebounce time.Duration
67
71
OIDCConfig httpmw.OAuth2Config
@@ -193,6 +197,11 @@ func (server *Server) AcquireJob(ctx context.Context, _ *proto.Empty) (*proto.Ac
193
197
}
194
198
}
195
199
200
+ sessionToken , err := server .regenerateSessionToken (ctx , owner , workspace )
201
+ if err != nil {
202
+ return nil , failJob (fmt .Sprintf ("regenerate session token: %s" , err ))
203
+ }
204
+
196
205
// Compute parameters for the workspace to consume.
197
206
parameters , err := parameter .Compute (ctx , server .Database , parameter.ComputeScope {
198
207
TemplateImportJobID : templateVersion .JobID ,
@@ -286,6 +295,7 @@ func (server *Server) AcquireJob(ctx context.Context, _ *proto.Empty) (*proto.Ac
286
295
WorkspaceOwnerId : owner .ID .String (),
287
296
TemplateName : template .Name ,
288
297
TemplateVersion : templateVersion .Name ,
298
+ CoderSessionToken : sessionToken ,
289
299
},
290
300
LogLevel : input .LogLevel ,
291
301
},
@@ -1410,6 +1420,79 @@ func InsertWorkspaceResource(ctx context.Context, db database.Store, jobID uuid.
1410
1420
return nil
1411
1421
}
1412
1422
1423
+ func workspaceSessionTokenName (workspace database.Workspace ) string {
1424
+ return fmt .Sprintf ("%s_%s_session_token" , workspace .OwnerID , workspace .ID )
1425
+ }
1426
+
1427
+ // Generates a new ID and secret for an API key.
1428
+ // TODO put API key logic in separate package.
1429
+ func GenerateAPIKeyIDSecret () (id string , secret string , err error ) {
1430
+ // Length of an API Key ID.
1431
+ id , err = cryptorand .String (10 )
1432
+ if err != nil {
1433
+ return "" , "" , err
1434
+ }
1435
+ // Length of an API Key secret.
1436
+ secret , err = cryptorand .String (22 )
1437
+ if err != nil {
1438
+ return "" , "" , err
1439
+ }
1440
+ return id , secret , nil
1441
+ }
1442
+
1443
+ func (server * Server ) regenerateSessionToken (ctx context.Context , user database.User , workspace database.Workspace ) (string , error ) {
1444
+ id , secret , err := GenerateAPIKeyIDSecret ()
1445
+ if err != nil {
1446
+ return "" , xerrors .Errorf ("generate API key: %w" , err )
1447
+ }
1448
+ hashed := sha256 .Sum256 ([]byte (secret ))
1449
+
1450
+ err = server .Database .InTx (
1451
+ func (tx database.Store ) error {
1452
+ key , err := tx .GetAPIKeyByName (ctx , database.GetAPIKeyByNameParams {
1453
+ UserID : workspace .OwnerID ,
1454
+ TokenName : workspaceSessionTokenName (workspace ),
1455
+ })
1456
+ if err == nil {
1457
+ err = tx .DeleteAPIKeyByID (ctx , key .ID )
1458
+ if err != nil {
1459
+ return xerrors .Errorf ("delete api key: %w" , err )
1460
+ }
1461
+ }
1462
+ if err != nil && ! xerrors .Is (err , sql .ErrNoRows ) {
1463
+ return xerrors .Errorf ("get api key by name: %w" , err )
1464
+ }
1465
+
1466
+ ip := net .IPv4 (0 , 0 , 0 , 0 )
1467
+ bitlen := len (ip ) * 8
1468
+ _ , err = tx .InsertAPIKey (ctx , database.InsertAPIKeyParams {
1469
+ ID : id ,
1470
+ UserID : workspace .OwnerID ,
1471
+ LifetimeSeconds : int64 (server .DeploymentValues .SessionDuration .Value ().Seconds ()),
1472
+ ExpiresAt : database .Now ().Add (server .DeploymentValues .SessionDuration .Value ()).UTC (),
1473
+ IPAddress : pqtype.Inet {
1474
+ IPNet : net.IPNet {
1475
+ IP : ip ,
1476
+ Mask : net .CIDRMask (bitlen , bitlen ),
1477
+ },
1478
+ Valid : true ,
1479
+ },
1480
+ CreatedAt : database .Now (),
1481
+ UpdatedAt : database .Now (),
1482
+ HashedSecret : hashed [:],
1483
+ LoginType : user .LoginType ,
1484
+ Scope : database .APIKeyScopeAll ,
1485
+ TokenName : workspaceSessionTokenName (workspace ),
1486
+ })
1487
+
1488
+ return nil
1489
+ }, nil )
1490
+ if err != nil {
1491
+ return "" , xerrors .Errorf ("regenerate API key: %w" , err )
1492
+ }
1493
+ return fmt .Sprintf ("%s-%s" , id , secret ), nil
1494
+ }
1495
+
1413
1496
// obtainOIDCAccessToken returns a valid OpenID Connect access token
1414
1497
// for the user if it's able to obtain one, otherwise it returns an empty string.
1415
1498
func obtainOIDCAccessToken (ctx context.Context , db database.Store , oidcConfig httpmw.OAuth2Config , userID uuid.UUID ) (string , error ) {
0 commit comments