refactor: move parseSecret out of App.postgrest
parseSecret only needs to be computed once, after the config is read.
This commit is contained in:
committed by
Steve Chavez
parent
96a16a377f
commit
0ff05edd16
+4
-1
@@ -28,6 +28,7 @@ import Network.Wai.Handler.Warp (defaultSettings, runSettings,
|
|||||||
import System.IO (BufferMode (..), hSetBuffering)
|
import System.IO (BufferMode (..), hSetBuffering)
|
||||||
|
|
||||||
import PostgREST.App (postgrest)
|
import PostgREST.App (postgrest)
|
||||||
|
import PostgREST.Auth (parseSecret)
|
||||||
import PostgREST.Config (AppConfig (..), configPoolTimeout',
|
import PostgREST.Config (AppConfig (..), configPoolTimeout',
|
||||||
prettyVersion, readAppConfig, readPathShowHelp, loadDbUriFile, loadSecretFile)
|
prettyVersion, readAppConfig, readPathShowHelp, loadDbUriFile, loadSecretFile)
|
||||||
import PostgREST.DbStructure (getDbStructure, getPgVersion)
|
import PostgREST.DbStructure (getDbStructure, getPgVersion)
|
||||||
@@ -203,7 +204,9 @@ main = do
|
|||||||
path <- readPathShowHelp
|
path <- readPathShowHelp
|
||||||
|
|
||||||
-- build the 'AppConfig' from the config file path
|
-- build the 'AppConfig' from the config file path
|
||||||
conf <- loadDbUriFile =<< loadSecretFile =<< readAppConfig path
|
conf <- do
|
||||||
|
cnf <- loadDbUriFile =<< loadSecretFile =<< readAppConfig path
|
||||||
|
pure cnf { configJWKS = parseSecret <$> configJwtSecret cnf}
|
||||||
|
|
||||||
-- Checks that the provided proxy uri is formated correctly
|
-- Checks that the provided proxy uri is formated correctly
|
||||||
when (isMalformedProxyUri $ toS <$> configOpenAPIProxyUri conf) $
|
when (isMalformedProxyUri $ toS <$> configOpenAPIProxyUri conf) $
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ import PostgREST.ApiRequest (Action (..), ApiRequest (..),
|
|||||||
InvokeMethod (..), Target (..),
|
InvokeMethod (..), Target (..),
|
||||||
mutuallyAgreeable, userApiRequest)
|
mutuallyAgreeable, userApiRequest)
|
||||||
import PostgREST.Auth (attemptJwtClaims, containsRole,
|
import PostgREST.Auth (attemptJwtClaims, containsRole,
|
||||||
jwtClaims, parseSecret)
|
jwtClaims)
|
||||||
import PostgREST.Config (AppConfig (..))
|
import PostgREST.Config (AppConfig (..))
|
||||||
import PostgREST.DbRequestBuilder (mutateRequest, readRequest,
|
import PostgREST.DbRequestBuilder (mutateRequest, readRequest,
|
||||||
returningCols)
|
returningCols)
|
||||||
@@ -69,8 +69,7 @@ import Protolude.Conv (toS)
|
|||||||
|
|
||||||
postgrest :: AppConfig -> IORef (Maybe DbStructure) -> P.Pool -> IO UTCTime -> IO () -> Application
|
postgrest :: AppConfig -> IORef (Maybe DbStructure) -> P.Pool -> IO UTCTime -> IO () -> Application
|
||||||
postgrest conf refDbStructure pool getTime worker =
|
postgrest conf refDbStructure pool getTime worker =
|
||||||
let middle = (if configQuiet conf then id else logStdout) . defaultMiddle
|
let middle = (if configQuiet conf then id else logStdout) . defaultMiddle in
|
||||||
jwtSecret = parseSecret <$> configJwtSecret conf in
|
|
||||||
middle $ \ req respond -> do
|
middle $ \ req respond -> do
|
||||||
time <- getTime
|
time <- getTime
|
||||||
body <- strictRequestBody req
|
body <- strictRequestBody req
|
||||||
@@ -86,7 +85,7 @@ postgrest conf refDbStructure pool getTime worker =
|
|||||||
Left err -> return . errorResponseFor $ err
|
Left err -> return . errorResponseFor $ err
|
||||||
Right (apiRequest, maybeCols) -> do
|
Right (apiRequest, maybeCols) -> do
|
||||||
-- The jwt must be checked before touching the db.
|
-- The jwt must be checked before touching the db.
|
||||||
attempt <- attemptJwtClaims jwtSecret (configJwtAudience conf) (toS $ iJWT apiRequest) time (rightToMaybe $ configRoleClaimKey conf)
|
attempt <- attemptJwtClaims (configJWKS conf) (configJwtAudience conf) (toS $ iJWT apiRequest) time (rightToMaybe $ configRoleClaimKey conf)
|
||||||
case jwtClaims attempt of
|
case jwtClaims attempt of
|
||||||
Left errJwt -> return . errorResponseFor $ errJwt
|
Left errJwt -> return . errorResponseFor $ errJwt
|
||||||
Right claims -> do
|
Right claims -> do
|
||||||
|
|||||||
@@ -58,14 +58,14 @@ jwtClaims attempt =
|
|||||||
-}
|
-}
|
||||||
attemptJwtClaims :: Maybe JWKSet -> Maybe StringOrURI -> LByteString -> UTCTime -> Maybe JSPath -> IO JWTAttempt
|
attemptJwtClaims :: Maybe JWKSet -> Maybe StringOrURI -> LByteString -> UTCTime -> Maybe JSPath -> IO JWTAttempt
|
||||||
attemptJwtClaims _ _ "" _ _ = return $ JWTClaims M.empty
|
attemptJwtClaims _ _ "" _ _ = return $ JWTClaims M.empty
|
||||||
attemptJwtClaims secret audience payload time jspath =
|
attemptJwtClaims maybeSecret audience payload time jspath =
|
||||||
case secret of
|
case maybeSecret of
|
||||||
Nothing -> return JWTMissingSecret
|
Nothing -> return JWTMissingSecret
|
||||||
Just s -> do
|
Just secret -> do
|
||||||
let validation = set allowedSkew 1 $ defaultJWTValidationSettings (maybe (const True) (==) audience)
|
let validation = set allowedSkew 1 $ defaultJWTValidationSettings (maybe (const True) (==) audience)
|
||||||
eJwt <- runExceptT $ do
|
eJwt <- runExceptT $ do
|
||||||
jwt <- decodeCompact payload
|
jwt <- decodeCompact payload
|
||||||
verifyClaimsAt validation s time jwt
|
verifyClaimsAt validation secret time jwt
|
||||||
return $ case eJwt of
|
return $ case eJwt of
|
||||||
Left e -> JWTInvalid e
|
Left e -> JWTInvalid e
|
||||||
Right jwt -> JWTClaims $ claims2map jwt jspath
|
Right jwt -> JWTClaims $ claims2map jwt jspath
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ module PostgREST.Config ( prettyVersion
|
|||||||
)
|
)
|
||||||
where
|
where
|
||||||
|
|
||||||
|
import Crypto.JWT (JWKSet)
|
||||||
import qualified Data.ByteString as B
|
import qualified Data.ByteString as B
|
||||||
import qualified Data.ByteString.Base64 as B64
|
import qualified Data.ByteString.Base64 as B64
|
||||||
import qualified Data.ByteString.Char8 as BS
|
import qualified Data.ByteString.Char8 as BS
|
||||||
@@ -97,7 +98,7 @@ data AppConfig = AppConfig {
|
|||||||
, configRootSpec :: Maybe Text
|
, configRootSpec :: Maybe Text
|
||||||
, configRawMediaTypes :: [B.ByteString]
|
, configRawMediaTypes :: [B.ByteString]
|
||||||
|
|
||||||
, configPath :: Maybe FilePath
|
, configJWKS :: Maybe JWKSet
|
||||||
}
|
}
|
||||||
|
|
||||||
configPoolTimeout' :: (Fractional a) => AppConfig -> a
|
configPoolTimeout' :: (Fractional a) => AppConfig -> a
|
||||||
@@ -230,14 +231,14 @@ readAppConfig cfgPath = do
|
|||||||
, Handler (\(C.ParseError err) -> exitErr $ "Error parsing config file:\n" <> err)
|
, Handler (\(C.ParseError err) -> exitErr $ "Error parsing config file:\n" <> err)
|
||||||
]
|
]
|
||||||
|
|
||||||
case C.runParser (parseConfig cfgPath) conf of
|
case C.runParser parseConfig conf of
|
||||||
Left err ->
|
Left err ->
|
||||||
exitErr $ "Error parsing config file:\n\t" <> err
|
exitErr $ "Error parsing config file:\n\t" <> err
|
||||||
Right appConf ->
|
Right appConf ->
|
||||||
return appConf
|
return appConf
|
||||||
|
|
||||||
where
|
where
|
||||||
parseConfig path =
|
parseConfig =
|
||||||
AppConfig
|
AppConfig
|
||||||
<$> reqString "db-uri"
|
<$> reqString "db-uri"
|
||||||
<*> reqString "db-anon-role"
|
<*> reqString "db-anon-role"
|
||||||
@@ -262,7 +263,7 @@ readAppConfig cfgPath = do
|
|||||||
<*> (maybe ["public"] splitOnCommas <$> optValue "db-extra-search-path")
|
<*> (maybe ["public"] splitOnCommas <$> optValue "db-extra-search-path")
|
||||||
<*> optString "root-spec"
|
<*> optString "root-spec"
|
||||||
<*> (maybe [] (fmap encodeUtf8 . splitOnCommas) <$> optValue "raw-media-types")
|
<*> (maybe [] (fmap encodeUtf8 . splitOnCommas) <$> optValue "raw-media-types")
|
||||||
<*> pure (Just path)
|
<*> pure Nothing
|
||||||
|
|
||||||
parseSocketFileMode :: C.Key -> C.Parser C.Config (Either Text FileMode)
|
parseSocketFileMode :: C.Key -> C.Parser C.Config (Either Text FileMode)
|
||||||
parseSocketFileMode k =
|
parseSocketFileMode k =
|
||||||
|
|||||||
+27
-17
@@ -22,6 +22,7 @@ import Test.Hspec
|
|||||||
import Test.Hspec.Wai
|
import Test.Hspec.Wai
|
||||||
import Text.Heredoc
|
import Text.Heredoc
|
||||||
|
|
||||||
|
import PostgREST.Auth (parseSecret)
|
||||||
import PostgREST.Config (AppConfig (..))
|
import PostgREST.Config (AppConfig (..))
|
||||||
import PostgREST.Types (JSPathExp (..))
|
import PostgREST.Types (JSPathExp (..))
|
||||||
import Protolude hiding (toS)
|
import Protolude hiding (toS)
|
||||||
@@ -64,6 +65,7 @@ getEnvVarWithDefault var def = toS <$>
|
|||||||
|
|
||||||
_baseCfg :: AppConfig
|
_baseCfg :: AppConfig
|
||||||
_baseCfg = -- Connection Settings
|
_baseCfg = -- Connection Settings
|
||||||
|
let secret = Just $ encodeUtf8 "reallyreallyreallyreallyverysafe" in
|
||||||
AppConfig mempty "postgrest_test_anonymous" Nothing (fromList ["test"]) "localhost" 3000
|
AppConfig mempty "postgrest_test_anonymous" Nothing (fromList ["test"]) "localhost" 3000
|
||||||
-- No user configured Unix Socket
|
-- No user configured Unix Socket
|
||||||
Nothing
|
Nothing
|
||||||
@@ -74,7 +76,7 @@ _baseCfg = -- Connection Settings
|
|||||||
-- db-channel-enabled
|
-- db-channel-enabled
|
||||||
False
|
False
|
||||||
-- Jwt settings
|
-- Jwt settings
|
||||||
(Just $ encodeUtf8 "reallyreallyreallyreallyverysafe") False Nothing
|
secret False Nothing
|
||||||
-- Connection Modifiers
|
-- Connection Modifiers
|
||||||
10 10 Nothing (Just "test.switch_role")
|
10 10 Nothing (Just "test.switch_role")
|
||||||
-- Debug Settings
|
-- Debug Settings
|
||||||
@@ -90,14 +92,14 @@ _baseCfg = -- Connection Settings
|
|||||||
Nothing
|
Nothing
|
||||||
-- Raw output media types
|
-- Raw output media types
|
||||||
[]
|
[]
|
||||||
-- Config path
|
-- Config JWK
|
||||||
Nothing
|
(parseSecret <$> secret)
|
||||||
|
|
||||||
testCfg :: Text -> AppConfig
|
testCfg :: Text -> AppConfig
|
||||||
testCfg testDbConn = _baseCfg { configDbUri = testDbConn }
|
testCfg testDbConn = _baseCfg { configDbUri = testDbConn }
|
||||||
|
|
||||||
testCfgNoJWT :: Text -> AppConfig
|
testCfgNoJWT :: Text -> AppConfig
|
||||||
testCfgNoJWT testDbConn = (testCfg testDbConn) { configJwtSecret = Nothing }
|
testCfgNoJWT testDbConn = (testCfg testDbConn) { configJwtSecret = Nothing, configJWKS = Nothing }
|
||||||
|
|
||||||
testUnicodeCfg :: Text -> AppConfig
|
testUnicodeCfg :: Text -> AppConfig
|
||||||
testUnicodeCfg testDbConn = (testCfg testDbConn) { configSchemas = fromList ["تست"] }
|
testUnicodeCfg testDbConn = (testCfg testDbConn) { configSchemas = fromList ["تست"] }
|
||||||
@@ -109,28 +111,36 @@ testProxyCfg :: Text -> AppConfig
|
|||||||
testProxyCfg testDbConn = (testCfg testDbConn) { configOpenAPIProxyUri = Just "https://postgrest.com/openapi.json" }
|
testProxyCfg testDbConn = (testCfg testDbConn) { configOpenAPIProxyUri = Just "https://postgrest.com/openapi.json" }
|
||||||
|
|
||||||
testCfgBinaryJWT :: Text -> AppConfig
|
testCfgBinaryJWT :: Text -> AppConfig
|
||||||
testCfgBinaryJWT testDbConn = (testCfg testDbConn) {
|
testCfgBinaryJWT testDbConn =
|
||||||
configJwtSecret = Just . B64.decodeLenient $
|
let secret = Just . B64.decodeLenient $ "cmVhbGx5cmVhbGx5cmVhbGx5cmVhbGx5dmVyeXNhZmU=" in
|
||||||
"cmVhbGx5cmVhbGx5cmVhbGx5cmVhbGx5dmVyeXNhZmU="
|
(testCfg testDbConn) {
|
||||||
|
configJwtSecret = secret
|
||||||
|
, configJWKS = parseSecret <$> secret
|
||||||
}
|
}
|
||||||
|
|
||||||
testCfgAudienceJWT :: Text -> AppConfig
|
testCfgAudienceJWT :: Text -> AppConfig
|
||||||
testCfgAudienceJWT testDbConn = (testCfg testDbConn) {
|
testCfgAudienceJWT testDbConn =
|
||||||
configJwtSecret = Just . B64.decodeLenient $
|
let secret = Just . B64.decodeLenient $ "cmVhbGx5cmVhbGx5cmVhbGx5cmVhbGx5dmVyeXNhZmU=" in
|
||||||
"cmVhbGx5cmVhbGx5cmVhbGx5cmVhbGx5dmVyeXNhZmU=",
|
(testCfg testDbConn) {
|
||||||
configJwtAudience = Just "youraudience"
|
configJwtSecret = secret
|
||||||
|
, configJwtAudience = Just "youraudience"
|
||||||
|
, configJWKS = parseSecret <$> secret
|
||||||
}
|
}
|
||||||
|
|
||||||
testCfgAsymJWK :: Text -> AppConfig
|
testCfgAsymJWK :: Text -> AppConfig
|
||||||
testCfgAsymJWK testDbConn = (testCfg testDbConn) {
|
testCfgAsymJWK testDbConn =
|
||||||
configJwtSecret = Just $ encodeUtf8
|
let secret = Just $ encodeUtf8 [str|{"alg":"RS256","e":"AQAB","key_ops":["verify"],"kty":"RSA","n":"0etQ2Tg187jb04MWfpuogYGV75IFrQQBxQaGH75eq_FpbkyoLcEpRUEWSbECP2eeFya2yZ9vIO5ScD-lPmovePk4Aa4SzZ8jdjhmAbNykleRPCxMg0481kz6PQhnHRUv3nF5WP479CnObJKqTVdEagVL66oxnX9VhZG9IZA7k0Th5PfKQwrKGyUeTGczpOjaPqbxlunP73j9AfnAt4XCS8epa-n3WGz1j-wfpr_ys57Aq-zBCfqP67UYzNpeI1AoXsJhD9xSDOzvJgFRvc3vm2wjAW4LEMwi48rCplamOpZToIHEPIaPzpveYQwDnB1HFTR1ove9bpKJsHmi-e2uzQ","use":"sig"}|]
|
||||||
[str|{"alg":"RS256","e":"AQAB","key_ops":["verify"],"kty":"RSA","n":"0etQ2Tg187jb04MWfpuogYGV75IFrQQBxQaGH75eq_FpbkyoLcEpRUEWSbECP2eeFya2yZ9vIO5ScD-lPmovePk4Aa4SzZ8jdjhmAbNykleRPCxMg0481kz6PQhnHRUv3nF5WP479CnObJKqTVdEagVL66oxnX9VhZG9IZA7k0Th5PfKQwrKGyUeTGczpOjaPqbxlunP73j9AfnAt4XCS8epa-n3WGz1j-wfpr_ys57Aq-zBCfqP67UYzNpeI1AoXsJhD9xSDOzvJgFRvc3vm2wjAW4LEMwi48rCplamOpZToIHEPIaPzpveYQwDnB1HFTR1ove9bpKJsHmi-e2uzQ","use":"sig"}|]
|
in (testCfg testDbConn) {
|
||||||
|
configJwtSecret = secret
|
||||||
|
, configJWKS = parseSecret <$> secret
|
||||||
}
|
}
|
||||||
|
|
||||||
testCfgAsymJWKSet :: Text -> AppConfig
|
testCfgAsymJWKSet :: Text -> AppConfig
|
||||||
testCfgAsymJWKSet testDbConn = (testCfg testDbConn) {
|
testCfgAsymJWKSet testDbConn =
|
||||||
configJwtSecret = Just $ encodeUtf8
|
let secret = Just $ encodeUtf8 [str|{"keys": [{"alg":"RS256","e":"AQAB","key_ops":["verify"],"kty":"RSA","n":"0etQ2Tg187jb04MWfpuogYGV75IFrQQBxQaGH75eq_FpbkyoLcEpRUEWSbECP2eeFya2yZ9vIO5ScD-lPmovePk4Aa4SzZ8jdjhmAbNykleRPCxMg0481kz6PQhnHRUv3nF5WP479CnObJKqTVdEagVL66oxnX9VhZG9IZA7k0Th5PfKQwrKGyUeTGczpOjaPqbxlunP73j9AfnAt4XCS8epa-n3WGz1j-wfpr_ys57Aq-zBCfqP67UYzNpeI1AoXsJhD9xSDOzvJgFRvc3vm2wjAW4LEMwi48rCplamOpZToIHEPIaPzpveYQwDnB1HFTR1ove9bpKJsHmi-e2uzQ","use":"sig"}]}|]
|
||||||
[str|{"keys": [{"alg":"RS256","e":"AQAB","key_ops":["verify"],"kty":"RSA","n":"0etQ2Tg187jb04MWfpuogYGV75IFrQQBxQaGH75eq_FpbkyoLcEpRUEWSbECP2eeFya2yZ9vIO5ScD-lPmovePk4Aa4SzZ8jdjhmAbNykleRPCxMg0481kz6PQhnHRUv3nF5WP479CnObJKqTVdEagVL66oxnX9VhZG9IZA7k0Th5PfKQwrKGyUeTGczpOjaPqbxlunP73j9AfnAt4XCS8epa-n3WGz1j-wfpr_ys57Aq-zBCfqP67UYzNpeI1AoXsJhD9xSDOzvJgFRvc3vm2wjAW4LEMwi48rCplamOpZToIHEPIaPzpveYQwDnB1HFTR1ove9bpKJsHmi-e2uzQ","use":"sig"}]}|]
|
in (testCfg testDbConn) {
|
||||||
|
configJwtSecret = secret
|
||||||
|
, configJWKS = parseSecret <$> secret
|
||||||
}
|
}
|
||||||
|
|
||||||
testNonexistentSchemaCfg :: Text -> AppConfig
|
testNonexistentSchemaCfg :: Text -> AppConfig
|
||||||
|
|||||||
Reference in New Issue
Block a user