mirror of
https://github.com/coder/coder.git
synced 2025-07-03 16:13:58 +00:00
This one aims to resolve #15604 Created some table tests for the main cases - also preferred to create two isolated cases for the most complicated cases in order to keep table tests simple enough. Give us full coverage on the middleware logic, for both optional and non optional cases - PSK and ProvisionerKey.
134 lines
4.3 KiB
Go
134 lines
4.3 KiB
Go
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))
|
|
}
|