这是indexloc提供的服务,不要输入任何密码
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions .circleci/test-server.sh
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,24 @@ run_pytest_parallel --hge-key="$HASURA_GRAPHQL_ADMIN_SECRET"

kill_hge_servers

##########
echo -e "\n$(time_elapsed): <########## TEST GRAPHQL-ENGINE WITH ADMIN SECRET AND UNAUTHORIZED ROLE #####################################>\n"
TEST_TYPE="admin-secret-unauthorized-role"

export HASURA_GRAPHQL_ADMIN_SECRET="HGE$RANDOM$RANDOM"
export HASURA_GRAPHQL_UNAUTHORIZED_ROLE="anonymous"

run_hge_with_args serve

wait_for_port 8080

pytest -n 1 -vv --hge-urls "$HGE_URL" --pg-urls "$HASURA_GRAPHQL_DATABASE_URL" --hge-key="$HASURA_GRAPHQL_ADMIN_SECRET" --test-unauthorized-role test_graphql_queries.py::TestUnauthorizedRolePermission

kill_hge_servers

unset HASURA_GRAPHQL_UNAUTHORIZED_ROLE


##########
echo -e "\n$(time_elapsed): <########## TEST GRAPHQL-ENGINE WITH ADMIN SECRET AND JWT #####################################>\n"
TEST_TYPE="jwt"
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ Read more about the session argument for computed fields in the [docs](https://h
### Bug fixes and improvements

(Add entries here in the order of: server, console, cli, docs, others)
- server: fix explain queries with role permissions (fix #4816)
- server: compile with GHC 8.10.1, closing a space leak with subscriptions. (close #4517) (#3388)

- server: fixes an issue where introspection queries with variables would fail because of caching (fix #4547)
Expand Down
61 changes: 61 additions & 0 deletions console/src/components/Main/Main.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import {
} from '../Common/utils/localStorageUtils';
import ToolTip from '../Common/Tooltip/Tooltip';
import { setPreReleaseNotificationOptOutInDB } from '../../telemetry/Actions';
import { Icon } from '../UIKit/atoms/Icon';

class Main extends React.Component {
constructor(props) {
Expand Down Expand Up @@ -651,6 +652,65 @@ class Main extends React.Component {
return null;
};

const getVulnerableVersionNotification = () => {
let vulnerableVersionNotificationHtml = null;

// vulnerable version to fixed version mapping
const vulnerableVersionsMapping = {
'v1.2.0-beta.5': 'v1.2.1',
'v1.2.0': 'v1.2.1',
};

if (Object.keys(vulnerableVersionsMapping).includes(serverVersion)) {
const fixedVersion = vulnerableVersionsMapping[serverVersion];

vulnerableVersionNotificationHtml = (
<div>
<div className={styles.phantom} />{' '}
{/* phantom div to prevent overlapping of banner with content. */}
<div
className={
styles.updateBannerWrapper +
' ' +
styles.vulnerableVersionBannerWrapper
}
>
<div className={styles.updateBanner}>
<span>
<Icon type={'warning'} /> <b>ATTENTION</b>
<span className={styles.middot}> &middot; </span>
This current server version has a security vulnerability.
Please upgrade to <b>{fixedVersion}</b> immediately
</span>
<span className={styles.middot}> &middot; </span>
<a
href={
'https://github.com/hasura/graphql-engine/releases/tag/' +
fixedVersion
}
target="_blank"
rel="noopener noreferrer"
>
<span>View Changelog</span>
</a>
<span className={styles.middot}> &middot; </span>
<a
className={styles.updateLink}
href="https://hasura.io/docs/1.0/graphql/manual/deployment/updating.html"
target="_blank"
rel="noopener noreferrer"
>
<span>Update Now</span>
</a>
</div>
</div>
</div>
);
}

return vulnerableVersionNotificationHtml;
};

return (
<div className={styles.container}>
<div className={styles.flexRow}>
Expand Down Expand Up @@ -816,6 +876,7 @@ class Main extends React.Component {
</div>

{getUpdateNotification()}
{getVulnerableVersionNotification()}
</div>
</div>
);
Expand Down
9 changes: 9 additions & 0 deletions console/src/components/Main/Main.scss
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,15 @@
}
}

.vulnerableVersionBannerWrapper {
background-color: #d9534f;
color: #eee;

.updateBanner a {
color: #eee;
}
}

.add_btn {
margin: 10px 0;
}
Expand Down
2 changes: 1 addition & 1 deletion server/src-lib/Hasura/GraphQL/Explain.hs
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ explainGQLQuery
-> GQLExplain
-> m EncJSON
explainGQLQuery pgExecCtx sc sqlGenCtx enableAL actionExecuter (GQLExplain query userVarsRaw) = do
userInfo <- mkUserInfo UAdminSecretSent sessionVariables $ Just adminRoleName
userInfo <- mkUserInfo (URBFromSessionVariablesFallback adminRoleName) UAdminSecretSent sessionVariables
(execPlan, queryReusability) <- runReusabilityT $
E.getExecPlanPartial userInfo sc enableAL query
(gCtx, rootSelSet) <- case execPlan of
Expand Down
63 changes: 31 additions & 32 deletions server/src-lib/Hasura/Server/Auth.hs
Original file line number Diff line number Diff line change
Expand Up @@ -149,52 +149,51 @@ getUserInfo
getUserInfo l m r a = fst <$> getUserInfoWithExpTime l m r a

getUserInfoWithExpTime
:: (HasVersion, MonadIO m, MonadError QErr m)
:: forall m. (HasVersion, MonadIO m, MonadError QErr m)
=> Logger Hasura
-> H.Manager
-> [N.Header]
-> AuthMode
-> m (UserInfo, Maybe UTCTime)
getUserInfoWithExpTime logger manager rawHeaders = \case

AMNoAuth -> (, Nothing) <$> userInfoFromHeaders UAuthNotSet
AMNoAuth -> withNoExpTime $ mkUserInfoFallbackAdminRole UAuthNotSet

AMAdminSecret adminScrt unAuthRole ->
case adminSecretM of
Just givenAdminScrt ->
withNoExpTime $ userInfoWhenAdminSecret adminScrt givenAdminScrt
Nothing ->
withNoExpTime $ userInfoWhenNoAdminSecret unAuthRole
AMAdminSecret adminSecretSet maybeUnauthRole ->
withAuthorization adminSecretSet $ withNoExpTime $
-- Consider unauthorized role, if not found raise admin secret header required exception
case maybeUnauthRole of
Nothing -> throw401 $ adminSecretHeader <> "/"
<> deprecatedAccessKeyHeader <> " required, but not found"
Just unAuthRole ->
mkUserInfo (URBPreDetermined unAuthRole) UAdminSecretNotSent sessionVariables

AMAdminSecretAndHook accKey hook ->
whenAdminSecretAbsent accKey $
userInfoFromAuthHook logger manager hook rawHeaders
AMAdminSecretAndHook adminSecretSet hook ->
withAuthorization adminSecretSet $ userInfoFromAuthHook logger manager hook rawHeaders

AMAdminSecretAndJWT accKey jwtSecret unAuthRole ->
whenAdminSecretAbsent accKey $ processJwt jwtSecret rawHeaders unAuthRole
AMAdminSecretAndJWT adminSecretSet jwtSecret unAuthRole ->
withAuthorization adminSecretSet $ processJwt jwtSecret rawHeaders unAuthRole

where
-- when admin secret is absent, run the action to retrieve UserInfo, otherwise
-- adminsecret override
whenAdminSecretAbsent ak action =
maybe action (withNoExpTime . userInfoWhenAdminSecret ak) adminSecretM

adminSecretM= foldl1 (<|>) $
map (`getSessionVariableValue` sessionVariables) [adminSecretHeader, deprecatedAccessKeyHeader]
mkUserInfoFallbackAdminRole adminSecretState =
mkUserInfo (URBFromSessionVariablesFallback adminRoleName)
adminSecretState sessionVariables

sessionVariables = mkSessionVariables rawHeaders

userInfoWhenAdminSecret key reqKey = do
when (reqKey /= getAdminSecret key) $ throw401 $
"invalid " <> adminSecretHeader <> "/" <> deprecatedAccessKeyHeader
userInfoFromHeaders UAdminSecretSent

userInfoWhenNoAdminSecret = \case
Nothing -> throw401 $ adminSecretHeader <> "/"
<> deprecatedAccessKeyHeader <> " required, but not found"
Just roleName -> mkUserInfo UAdminSecretNotSent sessionVariables $ Just roleName
withAuthorization
:: AdminSecret -> m (UserInfo, Maybe UTCTime) -> m (UserInfo, Maybe UTCTime)
withAuthorization adminSecretSet actionIfNoAdminSecret = do
let maybeRequestAdminSecret =
foldl1 (<|>) $ map (`getSessionVariableValue` sessionVariables)
[adminSecretHeader, deprecatedAccessKeyHeader]

-- when admin secret is absent, run the action to retrieve UserInfo
case maybeRequestAdminSecret of
Nothing -> actionIfNoAdminSecret
Just requestAdminSecret -> do
when (requestAdminSecret /= getAdminSecret adminSecretSet) $ throw401 $
"invalid " <> adminSecretHeader <> "/" <> deprecatedAccessKeyHeader
withNoExpTime $ mkUserInfoFallbackAdminRole UAdminSecretSent

withNoExpTime a = (, Nothing) <$> a

userInfoFromHeaders uas =
mkUserInfo uas sessionVariables $ Just adminRoleName
6 changes: 3 additions & 3 deletions server/src-lib/Hasura/Server/Auth/JWT.hs
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ processJwt jwtCtx headers mUnAuthRole =

withoutAuthZHeader = do
unAuthRole <- maybe missingAuthzHeader return mUnAuthRole
userInfo <- mkUserInfo UAdminSecretNotSent (mkSessionVariables headers) $ Just unAuthRole
userInfo <- mkUserInfo (URBPreDetermined unAuthRole) UAdminSecretNotSent $ mkSessionVariables headers
pure (userInfo, Nothing)

missingAuthzHeader =
Expand Down Expand Up @@ -271,8 +271,8 @@ processAuthZHeader jwtCtx headers authzHeader = do

-- transform the map of text:aeson-value -> text:text
metadata <- decodeJSON $ J.Object finalClaims
userInfo <- mkUserInfo UAdminSecretNotSent
(mkSessionVariablesText $ Map.toList metadata) $ Just roleName
userInfo <- mkUserInfo (URBPreDetermined roleName) UAdminSecretNotSent $
mkSessionVariablesText $ Map.toList metadata
pure (userInfo, expTimeM)
where
parseAuthzHeader = do
Expand Down
5 changes: 2 additions & 3 deletions server/src-lib/Hasura/Server/Auth/WebHook.hs
Original file line number Diff line number Diff line change
Expand Up @@ -116,9 +116,8 @@ mkUserInfoFromResp (Logger logger) url method statusCode respBody
throw500 "Invalid response from authorization hook"
where
getUserInfoFromHdrs rawHeaders = do
userInfo <- mkUserInfo UAdminSecretNotSent
(mkSessionVariablesText $ Map.toList rawHeaders)
Nothing
userInfo <- mkUserInfo URBFromSessionVariables UAdminSecretNotSent $
mkSessionVariablesText $ Map.toList rawHeaders
logWebHookResp LevelInfo Nothing Nothing
expiration <- runMaybeT $ timeFromCacheControl rawHeaders <|> timeFromExpires rawHeaders
pure (userInfo, expiration)
Expand Down
85 changes: 53 additions & 32 deletions server/src-lib/Hasura/Session.hs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ module Hasura.Session
, getSessionVariableValue
, getSessionVariables
, UserAdminSecret(..)
, UserRoleBuild(..)
, UserInfo
, _uiRole
, _uiSession
Expand Down Expand Up @@ -135,42 +136,62 @@ data UserInfo
} deriving (Show, Eq, Generic)
instance Hashable UserInfo

-- | Build user info from @'SessionVariables'
-- | Represents how to build a role from the session variables
data UserRoleBuild
= URBFromSessionVariables
-- ^ Look for `x-hasura-role` session variable value and absence will raise an exception
| URBFromSessionVariablesFallback !RoleName
-- ^ Look for `x-hasura-role` session variable value, if absent fall back to given role
| URBPreDetermined !RoleName
-- ^ Use only the pre-determined role
deriving (Show, Eq)

-- | Build @'UserInfo' from @'SessionVariables'
mkUserInfo
:: (MonadError QErr m)
=> UserAdminSecret
-> SessionVariables
-> Maybe RoleName -- ^ Default role if x-hasura-role session variable not found
-> m UserInfo
mkUserInfo userAdminSecret sess@(SessionVariables sessVars) defaultRole = do
roleName <- onNothing (maybeRoleFromSession <|> defaultRole) $
throw400 InvalidParams $ userRoleHeader <> " not found in session variables"
-- see Note [Backend only permissions] to know more about the following logic.
backendOnlyFieldAccess <- case userAdminSecret of
:: forall m. (MonadError QErr m)
=> UserRoleBuild -> UserAdminSecret -> SessionVariables -> m UserInfo
mkUserInfo roleBuild userAdminSecret sessionVariables = do
roleName <- case roleBuild of
URBFromSessionVariables -> onNothing maybeSessionRole $
throw400 InvalidParams $ userRoleHeader <> " not found in session variables"
URBFromSessionVariablesFallback role -> pure $ fromMaybe role maybeSessionRole
URBPreDetermined role -> pure role
backendOnlyFieldAccess <- getBackendOnlyFieldAccess
let modifiedSession = modifySessionVariables roleName sessionVariables
pure $ UserInfo roleName modifiedSession backendOnlyFieldAccess
where
maybeSessionRole = maybeRoleFromSessionVariables sessionVariables

-- | Add x-hasura-role header and remove admin secret headers
modifySessionVariables :: RoleName -> SessionVariables -> SessionVariables
modifySessionVariables roleName =
SessionVariables
. Map.insert userRoleHeader (roleNameToTxt roleName)
. Map.delete adminSecretHeader
. Map.delete deprecatedAccessKeyHeader
. unSessionVariables

-- | See Note [Backend only permissions] to know more about the function
getBackendOnlyFieldAccess :: m BackendOnlyFieldAccess
getBackendOnlyFieldAccess = case userAdminSecret of
UAdminSecretNotSent -> pure BOFADisallowed
UAdminSecretSent -> lookForBackendOnlyPermissionsConfig
UAuthNotSet -> lookForBackendOnlyPermissionsConfig
let modifiedSession = SessionVariables $ modifySessionVariables roleName sessVars
pure $ UserInfo roleName modifiedSession backendOnlyFieldAccess
where
-- Add x-hasura-role header and remove admin secret headers
modifySessionVariables roleName
= Map.insert userRoleHeader (roleNameToTxt roleName)
. Map.delete adminSecretHeader
. Map.delete deprecatedAccessKeyHeader

-- returns Nothing if x-hasura-role is an empty string
maybeRoleFromSession =
getSessionVariableValue userRoleHeader sess >>= mkRoleName

lookForBackendOnlyPermissionsConfig =
case getSessionVariableValue useBackendOnlyPermissionsHeader sess of
Nothing -> pure BOFADisallowed
Just varVal ->
case parseStringAsBool (T.unpack varVal) of
Left err -> throw400 BadRequest $
useBackendOnlyPermissionsHeader <> ": " <> T.pack err
Right privilege -> pure $ if privilege then BOFAAllowed else BOFADisallowed
where
lookForBackendOnlyPermissionsConfig =
case getSessionVariableValue useBackendOnlyPermissionsHeader sessionVariables of
Nothing -> pure BOFADisallowed
Just varVal ->
case parseStringAsBool (T.unpack varVal) of
Left err -> throw400 BadRequest $
useBackendOnlyPermissionsHeader <> ": " <> T.pack err
Right privilege -> pure $ if privilege then BOFAAllowed else BOFADisallowed


maybeRoleFromSessionVariables :: SessionVariables -> Maybe RoleName
maybeRoleFromSessionVariables sessionVariables =
-- returns Nothing if x-hasura-role is an empty string
getSessionVariableValue userRoleHeader sessionVariables >>= mkRoleName

adminUserInfo :: UserInfo
adminUserInfo = UserInfo adminRoleName mempty BOFADisallowed
6 changes: 6 additions & 0 deletions server/tests-py/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,12 @@ def pytest_addoption(parser):
help="When used along with collect-only, it will write the list of upgrade tests into the file specified"
)

parser.addoption(
"--test-unauthorized-role",
action="store_true",
help="Run testcases for unauthorized role",
)

#By default,
#1) Set default parallelism to one
#2) Set test grouping to by filename (--dist=loadfile)
Expand Down
Loading