feat: accept provisioner keys for provisioner auth (#13972)

This commit is contained in:
Garrett Delfosse
2024-07-25 10:22:55 -04:00
committed by GitHub
parent d488853393
commit ca83017dc1
9 changed files with 352 additions and 43 deletions
+7
View File
@@ -93,6 +93,13 @@ func CSRF(secureCookie bool) func(next http.Handler) http.Handler {
return true
}
if r.Header.Get(codersdk.ProvisionerDaemonKey) != "" {
// If present, the provisioner daemon also is providing an api key
// that will make them exempt from CSRF. But this is still useful
// for enumerating the external auths.
return true
}
// If the X-CSRF-TOKEN header is set, we can exempt the func if it's valid.
// This is the CSRF check.
sent := r.Header.Get("X-CSRF-TOKEN")
+82 -13
View File
@@ -8,6 +8,7 @@ import (
"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"
)
@@ -19,11 +20,13 @@ func ProvisionerDaemonAuthenticated(r *http.Request) bool {
}
type ExtractProvisionerAuthConfig struct {
DB database.Store
Optional bool
DB database.Store
Optional bool
PSK string
MultiOrgEnabled bool
}
func ExtractProvisionerDaemonAuthenticated(opts ExtractProvisionerAuthConfig, psk string) func(next http.Handler) http.Handler {
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()
@@ -36,37 +39,103 @@ func ExtractProvisionerDaemonAuthenticated(opts ExtractProvisionerAuthConfig, ps
httpapi.Write(ctx, w, code, response)
}
if psk == "" {
// No psk means external provisioner daemons are not allowed.
// So their auth is not valid.
if !opts.MultiOrgEnabled {
if opts.PSK == "" {
handleOptional(http.StatusUnauthorized, codersdk.Response{
Message: "External provisioner daemons not enabled",
})
return
}
fallbackToPSK(ctx, opts.PSK, next, w, r, handleOptional)
return
}
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: "External provisioner daemons not enabled",
Message: "provisioner daemon key and psk provided, but only one is allowed",
})
return
}
token := r.Header.Get(codersdk.ProvisionerDaemonPSK)
if token == "" {
id, keyValue, err := provisionerkey.Parse(key)
if err != nil {
handleOptional(http.StatusUnauthorized, codersdk.Response{
Message: "provisioner daemon auth token required",
Message: "provisioner daemon key invalid",
})
return
}
if subtle.ConstantTimeCompare([]byte(token), []byte(psk)) != 1 {
// nolint:gocritic // System must check if the provisioner key is valid.
pk, err := opts.DB.GetProvisionerKeyByID(dbauthz.AsSystemRestricted(ctx), id)
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: " + err.Error(),
})
return
}
if provisionerkey.Compare(pk.HashedSecret, provisionerkey.HashSecret(keyValue)) {
handleOptional(http.StatusUnauthorized, codersdk.Response{
Message: "provisioner daemon auth token invalid",
Message: "provisioner daemon key invalid",
})
return
}
// The PSK does not indicate a specific provisioner daemon. So just
// 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))
}