Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit b7eeb43

Browse files
authored
feat: Add ip_address to API keys (coder#2580)
Fixes coder#2561.
1 parent caf9c41 commit b7eeb43

File tree

13 files changed

+144
-49
lines changed

13 files changed

+144
-49
lines changed

coderd/database/databasefake/databasefake.go

+2
Original file line numberDiff line numberDiff line change
@@ -1382,6 +1382,7 @@ func (q *fakeQuerier) InsertAPIKey(_ context.Context, arg database.InsertAPIKeyP
13821382
ID: arg.ID,
13831383
LifetimeSeconds: arg.LifetimeSeconds,
13841384
HashedSecret: arg.HashedSecret,
1385+
IPAddress: arg.IPAddress,
13851386
UserID: arg.UserID,
13861387
ExpiresAt: arg.ExpiresAt,
13871388
CreatedAt: arg.CreatedAt,
@@ -1802,6 +1803,7 @@ func (q *fakeQuerier) UpdateAPIKeyByID(_ context.Context, arg database.UpdateAPI
18021803
}
18031804
apiKey.LastUsed = arg.LastUsed
18041805
apiKey.ExpiresAt = arg.ExpiresAt
1806+
apiKey.IPAddress = arg.IPAddress
18051807
apiKey.OAuthAccessToken = arg.OAuthAccessToken
18061808
apiKey.OAuthRefreshToken = arg.OAuthRefreshToken
18071809
apiKey.OAuthExpiry = arg.OAuthExpiry

coderd/database/dump.sql

+2-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
ALTER TABLE ONLY api_keys
2+
DROP COLUMN IF EXISTS ip_address;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
ALTER TABLE ONLY api_keys
2+
ADD COLUMN IF NOT EXISTS ip_address inet NOT NULL DEFAULT '0.0.0.0';

coderd/database/models.go

+14-13
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/database/queries.sql.go

+34-25
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/database/queries/apikeys.sql

+6-4
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ INSERT INTO
1717
id,
1818
lifetime_seconds,
1919
hashed_secret,
20+
ip_address,
2021
user_id,
2122
last_used,
2223
expires_at,
@@ -35,17 +36,18 @@ VALUES
3536
WHEN 0 THEN 86400
3637
ELSE @lifetime_seconds::bigint
3738
END
38-
, @hashed_secret, @user_id, @last_used, @expires_at, @created_at, @updated_at, @login_type, @oauth_access_token, @oauth_refresh_token, @oauth_id_token, @oauth_expiry) RETURNING *;
39+
, @hashed_secret, @ip_address, @user_id, @last_used, @expires_at, @created_at, @updated_at, @login_type, @oauth_access_token, @oauth_refresh_token, @oauth_id_token, @oauth_expiry) RETURNING *;
3940

4041
-- name: UpdateAPIKeyByID :exec
4142
UPDATE
4243
api_keys
4344
SET
4445
last_used = $2,
4546
expires_at = $3,
46-
oauth_access_token = $4,
47-
oauth_refresh_token = $5,
48-
oauth_expiry = $6
47+
ip_address = $4,
48+
oauth_access_token = $5,
49+
oauth_refresh_token = $6,
50+
oauth_expiry = $7
4951
WHERE
5052
id = $1;
5153

coderd/database/sqlc.yaml

+1
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,4 @@ rename:
2929
userstatus: UserStatus
3030
gitsshkey: GitSSHKey
3131
rbac_roles: RBACRoles
32+
ip_address: IPAddress

coderd/httpmw/apikey.go

+15
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,15 @@ import (
77
"database/sql"
88
"errors"
99
"fmt"
10+
"net"
1011
"net/http"
1112
"strings"
1213
"time"
1314

1415
"golang.org/x/oauth2"
1516

17+
"github.com/tabbed/pqtype"
18+
1619
"github.com/coder/coder/coderd/database"
1720
"github.com/coder/coder/coderd/httpapi"
1821
)
@@ -164,6 +167,17 @@ func ExtractAPIKey(db database.Store, oauth *OAuth2Configs) func(http.Handler) h
164167
// Only update LastUsed once an hour to prevent database spam.
165168
if now.Sub(key.LastUsed) > time.Hour {
166169
key.LastUsed = now
170+
remoteIP := net.ParseIP(r.RemoteAddr)
171+
if remoteIP == nil {
172+
remoteIP = net.IPv4(0, 0, 0, 0)
173+
}
174+
key.IPAddress = pqtype.Inet{
175+
IPNet: net.IPNet{
176+
IP: remoteIP,
177+
Mask: remoteIP.DefaultMask(),
178+
},
179+
Valid: true,
180+
}
167181
changed = true
168182
}
169183
// Only update the ExpiresAt once an hour to prevent database spam.
@@ -178,6 +192,7 @@ func ExtractAPIKey(db database.Store, oauth *OAuth2Configs) func(http.Handler) h
178192
ID: key.ID,
179193
LastUsed: key.LastUsed,
180194
ExpiresAt: key.ExpiresAt,
195+
IPAddress: key.IPAddress,
181196
OAuthAccessToken: key.OAuthAccessToken,
182197
OAuthRefreshToken: key.OAuthRefreshToken,
183198
OAuthExpiry: key.OAuthExpiry,

coderd/httpmw/apikey_test.go

+35
Original file line numberDiff line numberDiff line change
@@ -402,6 +402,41 @@ func TestAPIKey(t *testing.T) {
402402
require.Equal(t, token.Expiry, gotAPIKey.ExpiresAt)
403403
require.Equal(t, token.AccessToken, gotAPIKey.OAuthAccessToken)
404404
})
405+
406+
t.Run("RemoteIPUpdates", func(t *testing.T) {
407+
t.Parallel()
408+
var (
409+
db = databasefake.New()
410+
id, secret = randomAPIKeyParts()
411+
hashed = sha256.Sum256([]byte(secret))
412+
r = httptest.NewRequest("GET", "/", nil)
413+
rw = httptest.NewRecorder()
414+
user = createUser(r.Context(), t, db)
415+
)
416+
r.RemoteAddr = "1.1.1.1"
417+
r.AddCookie(&http.Cookie{
418+
Name: httpmw.SessionTokenKey,
419+
Value: fmt.Sprintf("%s-%s", id, secret),
420+
})
421+
422+
sentAPIKey, err := db.InsertAPIKey(r.Context(), database.InsertAPIKeyParams{
423+
ID: id,
424+
HashedSecret: hashed[:],
425+
LastUsed: database.Now().AddDate(0, 0, -1),
426+
ExpiresAt: database.Now().AddDate(0, 0, 1),
427+
UserID: user.ID,
428+
})
429+
require.NoError(t, err)
430+
httpmw.ExtractAPIKey(db, nil)(successHandler).ServeHTTP(rw, r)
431+
res := rw.Result()
432+
defer res.Body.Close()
433+
require.Equal(t, http.StatusOK, res.StatusCode)
434+
435+
gotAPIKey, err := db.GetAPIKeyByID(r.Context(), id)
436+
require.NoError(t, err)
437+
438+
require.NotEqual(t, sentAPIKey.IPAddress, gotAPIKey.IPAddress)
439+
})
405440
}
406441

407442
func createUser(ctx context.Context, t *testing.T, db database.Store) database.User {

coderd/telemetry/telemetry.go

+7-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"encoding/json"
88
"errors"
99
"fmt"
10+
"net"
1011
"net/http"
1112
"net/url"
1213
"runtime"
@@ -428,13 +429,17 @@ func (r *remoteReporter) createSnapshot() (*Snapshot, error) {
428429

429430
// ConvertAPIKey anonymizes an API key.
430431
func ConvertAPIKey(apiKey database.APIKey) APIKey {
431-
return APIKey{
432+
a := APIKey{
432433
ID: apiKey.ID,
433434
UserID: apiKey.UserID,
434435
CreatedAt: apiKey.CreatedAt,
435436
LastUsed: apiKey.LastUsed,
436437
LoginType: apiKey.LoginType,
437438
}
439+
if apiKey.IPAddress.Valid {
440+
a.IPAddress = apiKey.IPAddress.IPNet.IP
441+
}
442+
return a
438443
}
439444

440445
// ConvertWorkspace anonymizes a workspace.
@@ -616,6 +621,7 @@ type APIKey struct {
616621
CreatedAt time.Time `json:"created_at"`
617622
LastUsed time.Time `json:"last_used"`
618623
LoginType database.LoginType `json:"login_type"`
624+
IPAddress net.IP `json:"ip_address"`
619625
}
620626

621627
type User struct {

0 commit comments

Comments
 (0)