-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Expand file tree
/
Copy pathprovisionerdaemon.go
More file actions
133 lines (116 loc) · 4.3 KB
/
Copy pathprovisionerdaemon.go
File metadata and controls
133 lines (116 loc) · 4.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
package httpmw
import (
"context"
"crypto/subtle"
"net/http"
"github.com/coder/coder/v2/coderd/database"
"github.com/coder/coder/v2/coderd/database/dbauthz"
"github.com/coder/coder/v2/coderd/httpapi"
"github.com/coder/coder/v2/coderd/provisionerkey"
"github.com/coder/coder/v2/codersdk"
)
type provisionerDaemonContextKey struct{}
func ProvisionerDaemonAuthenticated(r *http.Request) bool {
proxy, ok := r.Context().Value(provisionerDaemonContextKey{}).(bool)
return ok && proxy
}
type ExtractProvisionerAuthConfig struct {
DB database.Store
Optional bool
PSK string
}
// ExtractProvisionerDaemonAuthenticated authenticates a request as a provisioner daemon.
// If the request is not authenticated, the next handler is called unless Optional is true.
// This function currently is tested inside the enterprise package.
func ExtractProvisionerDaemonAuthenticated(opts ExtractProvisionerAuthConfig) func(next http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
handleOptional := func(code int, response codersdk.Response) {
if opts.Optional {
next.ServeHTTP(w, r)
return
}
httpapi.Write(ctx, w, code, response)
}
psk := r.Header.Get(codersdk.ProvisionerDaemonPSK)
key := r.Header.Get(codersdk.ProvisionerDaemonKey)
if key == "" {
if opts.PSK == "" {
handleOptional(http.StatusUnauthorized, codersdk.Response{
Message: "provisioner daemon key required",
})
return
}
fallbackToPSK(ctx, opts.PSK, next, w, r, handleOptional)
return
}
if psk != "" {
handleOptional(http.StatusBadRequest, codersdk.Response{
Message: "provisioner daemon key and psk provided, but only one is allowed",
})
return
}
err := provisionerkey.Validate(key)
if err != nil {
handleOptional(http.StatusBadRequest, codersdk.Response{
Message: "provisioner daemon key invalid",
Detail: err.Error(),
})
return
}
hashedKey := provisionerkey.HashSecret(key)
// nolint:gocritic // System must check if the provisioner key is valid.
pk, err := opts.DB.GetProvisionerKeyByHashedSecret(dbauthz.AsSystemRestricted(ctx), hashedKey)
if err != nil {
if httpapi.Is404Error(err) {
handleOptional(http.StatusUnauthorized, codersdk.Response{
Message: "provisioner daemon key invalid",
})
return
}
handleOptional(http.StatusInternalServerError, codersdk.Response{
Message: "get provisioner daemon key",
Detail: err.Error(),
})
return
}
if provisionerkey.Compare(pk.HashedSecret, hashedKey) {
handleOptional(http.StatusUnauthorized, codersdk.Response{
Message: "provisioner daemon key invalid",
})
return
}
// The provisioner key does not indicate a specific provisioner daemon. So just
// store a boolean so the caller can check if the request is from an
// authenticated provisioner daemon.
ctx = context.WithValue(ctx, provisionerDaemonContextKey{}, true)
// store key used to authenticate the request
ctx = context.WithValue(ctx, provisionerKeyAuthContextKey{}, pk)
// nolint:gocritic // Authenticating as a provisioner daemon.
ctx = dbauthz.AsProvisionerd(ctx)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
}
type provisionerKeyAuthContextKey struct{}
func ProvisionerKeyAuthOptional(r *http.Request) (database.ProvisionerKey, bool) {
user, ok := r.Context().Value(provisionerKeyAuthContextKey{}).(database.ProvisionerKey)
return user, ok
}
func fallbackToPSK(ctx context.Context, psk string, next http.Handler, w http.ResponseWriter, r *http.Request, handleOptional func(code int, response codersdk.Response)) {
token := r.Header.Get(codersdk.ProvisionerDaemonPSK)
if subtle.ConstantTimeCompare([]byte(token), []byte(psk)) != 1 {
handleOptional(http.StatusUnauthorized, codersdk.Response{
Message: "provisioner daemon psk invalid",
})
return
}
// The PSK does not indicate a specific provisioner daemon. So just
// store a boolean so the caller can check if the request is from an
// authenticated provisioner daemon.
ctx = context.WithValue(ctx, provisionerDaemonContextKey{}, true)
// nolint:gocritic // Authenticating as a provisioner daemon.
ctx = dbauthz.AsProvisionerd(ctx)
next.ServeHTTP(w, r.WithContext(ctx))
}