Make internal endpoints available to nodes only

This commit is contained in:
eikek
2021-11-08 00:13:02 +01:00
parent 61c01ad79b
commit 7b8afe8371
14 changed files with 261 additions and 17 deletions

View File

@ -18,6 +18,7 @@ import docspell.extract.ExtractConfig
import docspell.ftssolr.SolrConfig
import docspell.joex.analysis.RegexNerFile
import docspell.joex.hk.HouseKeepingConfig
import docspell.joex.routes.InternalHeader
import docspell.joex.scheduler.{PeriodicSchedulerConfig, SchedulerConfig}
import docspell.joex.updatecheck.UpdateCheckConfig
import docspell.pubsub.naive.PubSubConfig
@ -42,8 +43,13 @@ case class Config(
updateCheck: UpdateCheckConfig
) {
def pubSubConfig: PubSubConfig =
PubSubConfig(appId, baseUrl / "internal" / "pubsub", 100)
def pubSubConfig(headerValue: Ident): PubSubConfig =
PubSubConfig(
appId,
baseUrl / "internal" / "pubsub",
100,
InternalHeader.header(headerValue.id)
)
}
object Config {

View File

@ -16,6 +16,7 @@ import docspell.common.Pools
import docspell.joex.routes._
import docspell.pubsub.naive.NaivePubSub
import docspell.store.Store
import docspell.store.records.RInternalSetting
import org.http4s.HttpApp
import org.http4s.blaze.client.BlazeClientBuilder
@ -43,13 +44,20 @@ object JoexServer {
cfg.files.chunkSize,
pools.connectEC
)
settings <- Resource.eval(store.transact(RInternalSetting.create))
httpClient <- BlazeClientBuilder[F].resource
pubSub <- NaivePubSub(cfg.pubSubConfig, store, httpClient)(Topics.all.map(_.topic))
pubSub <- NaivePubSub(
cfg.pubSubConfig(settings.internalRouteKey),
store,
httpClient
)(Topics.all.map(_.topic))
joexApp <- JoexAppImpl.create[F](cfg, signal, store, httpClient, pubSub)
httpApp = Router(
"/internal/pubsub" -> pubSub.receiveRoute,
"/internal" -> InternalHeader(settings.internalRouteKey) {
Router("pubsub" -> pubSub.receiveRoute)
},
"/api/info" -> InfoRoutes(cfg),
"/api/v1" -> JoexRoutes(joexApp)
).orNotFound

View File

@ -0,0 +1,59 @@
/*
* Copyright 2020 Eike K. & Contributors
*
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package docspell.joex.routes
import cats.data.{Kleisli, OptionT}
import cats.effect.kernel.Async
import cats.implicits._
import docspell.common.Ident
import org.http4s._
import org.http4s.dsl.Http4sDsl
import org.http4s.server.AuthMiddleware
import org.typelevel.ci._
// duplicated in restserver project
object InternalHeader {
private val headerName = ci"Docspell-Internal-Api"
def header(value: String): Header.Raw =
Header.Raw(headerName, value)
def apply[F[_]: Async](key: Ident)(routes: HttpRoutes[F]): HttpRoutes[F] = {
val dsl: Http4sDsl[F] = new Http4sDsl[F] {}
import dsl._
val authUser = checkSecret[F](key)
val onFailure: AuthedRoutes[String, F] =
Kleisli(req => OptionT.liftF(NotFound(req.context)))
val middleware: AuthMiddleware[F, Unit] =
AuthMiddleware(authUser, onFailure)
middleware(AuthedRoutes(authReq => routes.run(authReq.req)))
}
private def checkSecret[F[_]: Async](
key: Ident
): Kleisli[F, Request[F], Either[String, Unit]] =
Kleisli(req =>
extractSecret[F](req)
.filter(compareSecret(key.id))
.toRight("Secret invalid")
.map(_ => ())
.pure[F]
)
private def extractSecret[F[_]](req: Request[F]): Option[String] =
req.headers.get(headerName).map(_.head.value)
private def compareSecret(s1: String)(s2: String): Boolean =
s1 == s2
}