package httpmw import ( "context" "crypto/subtle" "net/http" "golang.org/x/xerrors" "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/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 } func ExtractProvisionerDaemonAuthenticated(opts ExtractProvisionerAuthConfig, psk string) 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) } if psk == "" { // No psk means external provisioner daemons are not allowed. // So their auth is not valid. handleOptional(http.StatusBadRequest, codersdk.Response{ Message: "External provisioner daemons not enabled", }) return } token := r.Header.Get(codersdk.ProvisionerDaemonPSK) if token == "" { handleOptional(http.StatusUnauthorized, codersdk.Response{ Message: "provisioner daemon auth token required", }) return } if subtle.ConstantTimeCompare([]byte(token), []byte(psk)) != 1 { handleOptional(http.StatusUnauthorized, codersdk.Response{ Message: "provisioner daemon auth token 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) subj, ok := dbauthz.ActorFromContext(ctx) if !ok { // This should never happen httpapi.InternalServerError(w, xerrors.New("developer error: ExtractProvisionerDaemonAuth missing rbac actor")) } // Use the same subject for the userAuthKey ctx = context.WithValue(ctx, userAuthKey{}, Authorization{ Actor: subj, ActorName: "provisioner_daemon", }) next.ServeHTTP(w, r.WithContext(ctx)) }) } }