Skip to content

Commit 49a2ec7

Browse files
committed
Merge branch 'main' into dean/schedule-max-ttl
2 parents 6caeb00 + 9ea21bf commit 49a2ec7

19 files changed

+316
-126
lines changed

.github/workflows/contrib.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ jobs:
3333
steps:
3434
- name: cla
3535
if: (github.event.comment.body == 'recheck' || github.event.comment.body == 'I have read the CLA Document and I hereby sign the CLA') || github.event_name == 'pull_request_target'
36-
uses: contributor-assistant/github-action@v2.2.1
36+
uses: contributor-assistant/github-action@v2.3.0
3737
env:
3838
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
3939
# the below token should have repo scope and must be manually added by you in the repository's secret

.github/workflows/release.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -275,7 +275,7 @@ jobs:
275275
276276
- name: Upload artifacts to actions (if dry-run)
277277
if: ${{ inputs.dry_run }}
278-
uses: actions/upload-artifact@v2
278+
uses: actions/upload-artifact@v3
279279
with:
280280
name: release-artifacts
281281
path: |

.github/workflows/security.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ jobs:
130130
category: "Trivy"
131131

132132
- name: Upload Trivy scan results as an artifact
133-
uses: actions/upload-artifact@v2
133+
uses: actions/upload-artifact@v3
134134
with:
135135
name: trivy
136136
path: trivy-results.sarif

.vscode/settings.json

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
"agentsdk",
55
"apps",
66
"ASKPASS",
7+
"authcheck",
78
"autostop",
89
"awsidentity",
910
"bodyclose",

cli/tokens.go

+4-17
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import (
66
"strings"
77
"time"
88

9-
"github.com/google/uuid"
109
"github.com/spf13/cobra"
1110
"golang.org/x/exp/slices"
1211
"golang.org/x/xerrors"
@@ -99,16 +98,14 @@ type tokenListRow struct {
9998
Owner string `json:"-" table:"owner"`
10099
}
101100

102-
func tokenListRowFromToken(token codersdk.APIKey, usersByID map[uuid.UUID]codersdk.User) tokenListRow {
103-
user := usersByID[token.UserID]
104-
101+
func tokenListRowFromToken(token codersdk.APIKeyWithOwner) tokenListRow {
105102
return tokenListRow{
106-
APIKey: token,
103+
APIKey: token.APIKey,
107104
ID: token.ID,
108105
LastUsed: token.LastUsed,
109106
ExpiresAt: token.ExpiresAt,
110107
CreatedAt: token.CreatedAt,
111-
Owner: user.Username,
108+
Owner: token.Username,
112109
}
113110
}
114111

@@ -150,20 +147,10 @@ func listTokens() *cobra.Command {
150147
))
151148
}
152149

153-
userRes, err := client.Users(cmd.Context(), codersdk.UsersRequest{})
154-
if err != nil {
155-
return err
156-
}
157-
158-
usersByID := map[uuid.UUID]codersdk.User{}
159-
for _, user := range userRes.Users {
160-
usersByID[user.ID] = user
161-
}
162-
163150
displayTokens = make([]tokenListRow, len(tokens))
164151

165152
for i, token := range tokens {
166-
displayTokens[i] = tokenListRowFromToken(token, usersByID)
153+
displayTokens[i] = tokenListRowFromToken(token)
167154
}
168155

169156
out, err := formatter.Format(cmd.Context(), displayTokens)

coderd/apikey.go

+23-2
Original file line numberDiff line numberDiff line change
@@ -216,9 +216,30 @@ func (api *API) tokens(rw http.ResponseWriter, r *http.Request) {
216216
return
217217
}
218218

219-
var apiKeys []codersdk.APIKey
219+
var userIds []uuid.UUID
220220
for _, key := range keys {
221-
apiKeys = append(apiKeys, convertAPIKey(key))
221+
userIds = append(userIds, key.UserID)
222+
}
223+
224+
users, _ := api.Database.GetUsersByIDs(ctx, userIds)
225+
usersByID := map[uuid.UUID]database.User{}
226+
for _, user := range users {
227+
usersByID[user.ID] = user
228+
}
229+
230+
var apiKeys []codersdk.APIKeyWithOwner
231+
for _, key := range keys {
232+
if user, exists := usersByID[key.UserID]; exists {
233+
apiKeys = append(apiKeys, codersdk.APIKeyWithOwner{
234+
APIKey: convertAPIKey(key),
235+
Username: user.Username,
236+
})
237+
} else {
238+
apiKeys = append(apiKeys, codersdk.APIKeyWithOwner{
239+
APIKey: convertAPIKey(key),
240+
Username: "",
241+
})
242+
}
222243
}
223244

224245
httpapi.Write(ctx, rw, http.StatusOK, apiKeys)

coderd/azureidentity/azureidentity.go

+73-20
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
11
package azureidentity
22

33
import (
4-
"context"
54
"crypto/x509"
65
"encoding/base64"
76
"encoding/json"
8-
"io"
9-
"net/http"
7+
"encoding/pem"
108
"regexp"
119

1210
"go.mozilla.org/pkcs7"
@@ -23,7 +21,7 @@ type metadata struct {
2321

2422
// Validate ensures the signature was signed by an Azure certificate.
2523
// It returns the associated VM ID if successful.
26-
func Validate(ctx context.Context, signature string, options x509.VerifyOptions) (string, error) {
24+
func Validate(signature string, options x509.VerifyOptions) (string, error) {
2725
data, err := base64.StdEncoding.DecodeString(signature)
2826
if err != nil {
2927
return "", xerrors.Errorf("decode base64: %w", err)
@@ -41,24 +39,14 @@ func Validate(ctx context.Context, signature string, options x509.VerifyOptions)
4139
}
4240
if options.Intermediates == nil {
4341
options.Intermediates = x509.NewCertPool()
44-
for _, certURL := range signer.IssuingCertificateURL {
45-
req, err := http.NewRequestWithContext(ctx, "GET", certURL, nil)
46-
if err != nil {
47-
return "", xerrors.Errorf("new request %q: %w", certURL, err)
48-
}
49-
res, err := http.DefaultClient.Do(req)
50-
if err != nil {
51-
return "", xerrors.Errorf("perform request %q: %w", certURL, err)
42+
for _, cert := range certificates {
43+
block, rest := pem.Decode([]byte(cert))
44+
if len(rest) != 0 {
45+
return "", xerrors.Errorf("invalid certificate. %d bytes remain", len(rest))
5246
}
53-
data, err := io.ReadAll(res.Body)
47+
cert, err := x509.ParseCertificate(block.Bytes)
5448
if err != nil {
55-
_ = res.Body.Close()
56-
return "", xerrors.Errorf("read body %q: %w", certURL, err)
57-
}
58-
_ = res.Body.Close()
59-
cert, err := x509.ParseCertificate(data)
60-
if err != nil {
61-
return "", xerrors.Errorf("parse certificate %q: %w", certURL, err)
49+
return "", xerrors.Errorf("parse certificate: %w", err)
6250
}
6351
options.Intermediates.AddCert(cert)
6452
}
@@ -76,3 +64,68 @@ func Validate(ctx context.Context, signature string, options x509.VerifyOptions)
7664
}
7765
return metadata.VMID, nil
7866
}
67+
68+
var certificates = []string{
69+
`-----BEGIN CERTIFICATE-----
70+
MIIFWjCCBEKgAwIBAgIQDxSWXyAgaZlP1ceseIlB4jANBgkqhkiG9w0BAQsFADBa
71+
MQswCQYDVQQGEwJJRTESMBAGA1UEChMJQmFsdGltb3JlMRMwEQYDVQQLEwpDeWJl
72+
clRydXN0MSIwIAYDVQQDExlCYWx0aW1vcmUgQ3liZXJUcnVzdCBSb290MB4XDTIw
73+
MDcyMTIzMDAwMFoXDTI0MTAwODA3MDAwMFowTzELMAkGA1UEBhMCVVMxHjAcBgNV
74+
BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEgMB4GA1UEAxMXTWljcm9zb2Z0IFJT
75+
QSBUTFMgQ0EgMDEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCqYnfP
76+
mmOyBoTzkDb0mfMUUavqlQo7Rgb9EUEf/lsGWMk4bgj8T0RIzTqk970eouKVuL5R
77+
IMW/snBjXXgMQ8ApzWRJCZbar879BV8rKpHoAW4uGJssnNABf2n17j9TiFy6BWy+
78+
IhVnFILyLNK+W2M3zK9gheiWa2uACKhuvgCca5Vw/OQYErEdG7LBEzFnMzTmJcli
79+
W1iCdXby/vI/OxbfqkKD4zJtm45DJvC9Dh+hpzqvLMiK5uo/+aXSJY+SqhoIEpz+
80+
rErHw+uAlKuHFtEjSeeku8eR3+Z5ND9BSqc6JtLqb0bjOHPm5dSRrgt4nnil75bj
81+
c9j3lWXpBb9PXP9Sp/nPCK+nTQmZwHGjUnqlO9ebAVQD47ZisFonnDAmjrZNVqEX
82+
F3p7laEHrFMxttYuD81BdOzxAbL9Rb/8MeFGQjE2Qx65qgVfhH+RsYuuD9dUw/3w
83+
ZAhq05yO6nk07AM9c+AbNtRoEcdZcLCHfMDcbkXKNs5DJncCqXAN6LhXVERCw/us
84+
G2MmCMLSIx9/kwt8bwhUmitOXc6fpT7SmFvRAtvxg84wUkg4Y/Gx++0j0z6StSeN
85+
0EJz150jaHG6WV4HUqaWTb98Tm90IgXAU4AW2GBOlzFPiU5IY9jt+eXC2Q6yC/Zp
86+
TL1LAcnL3Qa/OgLrHN0wiw1KFGD51WRPQ0Sh7QIDAQABo4IBJTCCASEwHQYDVR0O
87+
BBYEFLV2DDARzseSQk1Mx1wsyKkM6AtkMB8GA1UdIwQYMBaAFOWdWTCCR1jMrPoI
88+
VDaGezq1BE3wMA4GA1UdDwEB/wQEAwIBhjAdBgNVHSUEFjAUBggrBgEFBQcDAQYI
89+
KwYBBQUHAwIwEgYDVR0TAQH/BAgwBgEB/wIBADA0BggrBgEFBQcBAQQoMCYwJAYI
90+
KwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTA6BgNVHR8EMzAxMC+g
91+
LaArhilodHRwOi8vY3JsMy5kaWdpY2VydC5jb20vT21uaXJvb3QyMDI1LmNybDAq
92+
BgNVHSAEIzAhMAgGBmeBDAECATAIBgZngQwBAgIwCwYJKwYBBAGCNyoBMA0GCSqG
93+
SIb3DQEBCwUAA4IBAQCfK76SZ1vae4qt6P+dTQUO7bYNFUHR5hXcA2D59CJWnEj5
94+
na7aKzyowKvQupW4yMH9fGNxtsh6iJswRqOOfZYC4/giBO/gNsBvwr8uDW7t1nYo
95+
DYGHPpvnpxCM2mYfQFHq576/TmeYu1RZY29C4w8xYBlkAA8mDJfRhMCmehk7cN5F
96+
JtyWRj2cZj/hOoI45TYDBChXpOlLZKIYiG1giY16vhCRi6zmPzEwv+tk156N6cGS
97+
Vm44jTQ/rs1sa0JSYjzUaYngoFdZC4OfxnIkQvUIA4TOFmPzNPEFdjcZsgbeEz4T
98+
cGHTBPK4R28F44qIMCtHRV55VMX53ev6P3hRddJb
99+
-----END CERTIFICATE-----`,
100+
`-----BEGIN CERTIFICATE-----
101+
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
130+
-----END CERTIFICATE-----`,
131+
}

coderd/azureidentity/azureidentity_test.go

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package azureidentity_test
22

33
import (
4-
"context"
54
"crypto/x509"
65
"testing"
76
"time"
@@ -19,7 +18,7 @@ func TestValidate(t *testing.T) {
1918
t.Parallel()
2019
ct, err := time.Parse(time.RFC3339, "2023-02-01T00:00:00Z")
2120
require.NoError(t, err)
22-
vm, err := azureidentity.Validate(context.Background(), signature, x509.VerifyOptions{
21+
vm, err := azureidentity.Validate(signature, x509.VerifyOptions{
2322
CurrentTime: ct,
2423
})
2524
require.NoError(t, err)

coderd/workspaceresourceauth.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ func (api *API) postWorkspaceAuthAzureInstanceIdentity(rw http.ResponseWriter, r
3737
if !httpapi.Read(ctx, rw, r, &req) {
3838
return
3939
}
40-
instanceID, err := azureidentity.Validate(ctx, req.Signature, api.AzureCertificates)
40+
instanceID, err := azureidentity.Validate(req.Signature, api.AzureCertificates)
4141
if err != nil {
4242
httpapi.Write(ctx, rw, http.StatusUnauthorized, codersdk.Response{
4343
Message: "Invalid Azure identity.",

codersdk/apikey.go

+7-2
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,11 @@ type TokensFilter struct {
9090
IncludeAll bool `json:"include_all"`
9191
}
9292

93+
type APIKeyWithOwner struct {
94+
APIKey
95+
Username string `json:"username"`
96+
}
97+
9398
// asRequestOption returns a function that can be used in (*Client).Request.
9499
// It modifies the request query parameters.
95100
func (f TokensFilter) asRequestOption() RequestOption {
@@ -101,7 +106,7 @@ func (f TokensFilter) asRequestOption() RequestOption {
101106
}
102107

103108
// Tokens list machine API keys.
104-
func (c *Client) Tokens(ctx context.Context, userID string, filter TokensFilter) ([]APIKey, error) {
109+
func (c *Client) Tokens(ctx context.Context, userID string, filter TokensFilter) ([]APIKeyWithOwner, error) {
105110
res, err := c.Request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/users/%s/keys/tokens", userID), nil, filter.asRequestOption())
106111
if err != nil {
107112
return nil, err
@@ -110,7 +115,7 @@ func (c *Client) Tokens(ctx context.Context, userID string, filter TokensFilter)
110115
if res.StatusCode > http.StatusOK {
111116
return nil, ReadBodyAsError(res)
112117
}
113-
apiKey := []APIKey{}
118+
apiKey := []APIKeyWithOwner{}
114119
return apiKey, json.NewDecoder(res.Body).Decode(&apiKey)
115120
}
116121

site/src/api/api.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -142,8 +142,8 @@ export const getApiKey = async (): Promise<TypesGen.GenerateAPIKeyResponse> => {
142142

143143
export const getTokens = async (
144144
params: TypesGen.TokensFilter,
145-
): Promise<TypesGen.APIKey[]> => {
146-
const response = await axios.get<TypesGen.APIKey[]>(
145+
): Promise<TypesGen.APIKeyWithOwner[]> => {
146+
const response = await axios.get<TypesGen.APIKeyWithOwner[]>(
147147
`/api/v2/users/me/keys/tokens`,
148148
{
149149
params,

site/src/api/typesGenerated.ts

+5
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,11 @@ export interface APIKey {
1313
readonly lifetime_seconds: number
1414
}
1515

16+
// From codersdk/apikey.go
17+
export interface APIKeyWithOwner extends APIKey {
18+
readonly username: string
19+
}
20+
1621
// From codersdk/licenses.go
1722
export interface AddLicenseRequest {
1823
readonly license: string

site/src/i18n/en/tokensPage.json

+4-3
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
{
22
"title": "Tokens",
3-
"description": "Tokens are used to authenticate with the Coder API. You can create a token with the Coder CLI using the ",
3+
"description": "Tokens are used to authenticate with the Coder API. You can create a token with the Coder CLI using the <1>{{cliCreateCommand}}</1> command.",
44
"emptyState": "No tokens found",
55
"deleteToken": {
66
"delete": "Delete Token",
7-
"deleteCaption": "Are you sure you want to delete this token?",
7+
"deleteCaption": "Are you sure you want to delete this token?<br/><br/><4>{{tokenId}}</4>",
88
"deleteSuccess": "Token has been deleted",
99
"deleteFailure": "Failed to delete token"
1010
},
@@ -13,6 +13,7 @@
1313
"id": "ID",
1414
"createdAt": "Created At",
1515
"lastUsed": "Last Used",
16-
"expiresAt": "Expires At"
16+
"expiresAt": "Expires At",
17+
"owner": "Owner"
1718
}
1819
}

0 commit comments

Comments
 (0)