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

@ -14,6 +14,7 @@ import docspell.oidc.ProviderConfig
import docspell.pubsub.naive.PubSubConfig
import docspell.restserver.Config.OpenIdConfig
import docspell.restserver.auth.OpenId
import docspell.restserver.http4s.InternalHeader
import com.comcast.ip4s.IpAddress
@ -35,8 +36,13 @@ case class Config(
def openIdEnabled: Boolean =
openid.exists(_.enabled)
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

@ -19,12 +19,13 @@ import docspell.common._
import docspell.oidc.CodeFlowRoutes
import docspell.pubsub.naive.NaivePubSub
import docspell.restserver.auth.OpenId
import docspell.restserver.http4s.EnvMiddleware
import docspell.restserver.http4s.{EnvMiddleware, InternalHeader}
import docspell.restserver.routes._
import docspell.restserver.webapp._
import docspell.restserver.ws.OutputEvent.KeepAlive
import docspell.restserver.ws.{OutputEvent, WebSocketRoutes}
import docspell.store.Store
import docspell.store.records.RInternalSetting
import org.http4s._
import org.http4s.blaze.client.BlazeClientBuilder
@ -50,14 +51,14 @@ object RestServer {
server =
Stream
.resource(createApp(cfg, pools))
.flatMap { case (restApp, pubSub, httpClient) =>
.flatMap { case (restApp, pubSub, httpClient, setting) =>
Stream(
Subscriptions(wsTopic, restApp.backend.pubSub),
BlazeServerBuilder[F]
.bindHttp(cfg.bind.port, cfg.bind.address)
.withoutBanner
.withHttpWebSocketApp(
createHttpApp(cfg, httpClient, pubSub, restApp, wsTopic)
createHttpApp(cfg, setting, httpClient, pubSub, restApp, wsTopic)
)
.serve
.drain
@ -71,7 +72,7 @@ object RestServer {
def createApp[F[_]: Async](
cfg: Config,
pools: Pools
): Resource[F, (RestApp[F], NaivePubSub[F], Client[F])] =
): Resource[F, (RestApp[F], NaivePubSub[F], Client[F], RInternalSetting)] =
for {
httpClient <- BlazeClientBuilder[F].resource
store <- Store.create[F](
@ -79,12 +80,18 @@ object RestServer {
cfg.backend.files.chunkSize,
pools.connectEC
)
pubSub <- NaivePubSub(cfg.pubSubConfig, store, httpClient)(Topics.all.map(_.topic))
setting <- Resource.eval(store.transact(RInternalSetting.create))
pubSub <- NaivePubSub(
cfg.pubSubConfig(setting.internalRouteKey),
store,
httpClient
)(Topics.all.map(_.topic))
restApp <- RestAppImpl.create[F](cfg, store, httpClient, pubSub)
} yield (restApp, pubSub, httpClient)
} yield (restApp, pubSub, httpClient, setting)
def createHttpApp[F[_]: Async](
cfg: Config,
internSettings: RInternalSetting,
httpClient: Client[F],
pubSub: NaivePubSub[F],
restApp: RestApp[F],
@ -94,7 +101,9 @@ object RestServer {
) = {
val templates = TemplateRoutes[F](cfg)
val httpApp = Router(
"/internal/pubsub" -> pubSub.receiveRoute,
"/internal" -> InternalHeader(internSettings.internalRouteKey) {
internalRoutes(pubSub)
},
"/api/info" -> routes.InfoRoutes(),
"/api/v1/open/" -> openRoutes(cfg, httpClient, restApp),
"/api/v1/sec/" -> Authenticate(restApp.backend.login, cfg.auth) { token =>
@ -116,6 +125,11 @@ object RestServer {
Logger.httpApp(logHeaders = false, logBody = false)(httpApp)
}
def internalRoutes[F[_]: Async](pubSub: NaivePubSub[F]): HttpRoutes[F] =
Router(
"pubsub" -> pubSub.receiveRoute
)
def securedRoutes[F[_]: Async](
cfg: Config,
restApp: RestApp[F],

View File

@ -8,6 +8,7 @@ package docspell.restserver
import fs2.Stream
import fs2.concurrent.Topic
import docspell.backend.msg.JobDone
import docspell.common.ProcessItemArgs
import docspell.pubsub.api.PubSubT

View File

@ -0,0 +1,59 @@
/*
* Copyright 2020 Eike K. & Contributors
*
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package docspell.restserver.http4s
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 joex 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
}