refactor: move parseSecret out of App.postgrest

parseSecret only needs to be computed once, after the config is read.
This commit is contained in:
steve-chavez
2020-07-13 11:30:16 -05:00
committed by Steve Chavez
parent 96a16a377f
commit 0ff05edd16
5 changed files with 43 additions and 30 deletions
+4 -1
View File
@@ -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) $
+3 -4
View File
@@ -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
+4 -4
View File
@@ -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
+5 -4
View File
@@ -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
View File
@@ -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