Add key storage mode that replaces old keys instead of rejecting new ones
This commit is contained in:
parent
2e705b6868
commit
f09bdd4141
3 changed files with 225 additions and 79 deletions
|
@ -129,3 +129,7 @@ capability-signing-key: config/capability_signing_key
|
||||||
|
|
||||||
# Salt file for encoding and decoding hashids
|
# Salt file for encoding and decoding hashids
|
||||||
hashids-salt-file: config/hashids_salt
|
hashids-salt-file: config/hashids_salt
|
||||||
|
|
||||||
|
# Whether to reject an HTTP signature when we want to insert a new key or usage
|
||||||
|
# record but reached the limit setting
|
||||||
|
reject-on-max-keys: true
|
||||||
|
|
|
@ -597,29 +597,126 @@ instanceAndActor host luActor luInbox = do
|
||||||
idAndNew (Left (Entity iid _)) = (iid, False)
|
idAndNew (Left (Entity iid _)) = (iid, False)
|
||||||
idAndNew (Right iid) = (iid, True)
|
idAndNew (Right iid) = (iid, True)
|
||||||
|
|
||||||
actorRoom :: RemoteSharerId -> AppDB Bool
|
actorRoom :: Int -> RemoteSharerId -> AppDB Bool
|
||||||
actorRoom rsid = do
|
actorRoom limit rsid = do
|
||||||
mn <- getsYesod $ appMaxActorKeys . appSettings
|
sumUpTo limit
|
||||||
case mn of
|
(count [VerifKeySharedUsageUser ==. rsid])
|
||||||
Nothing -> pure True
|
(count [VerifKeySharer ==. Just rsid])
|
||||||
Just n ->
|
|
||||||
sumUpTo n
|
getOldUsageId rsid = fmap entityKey . listToMaybe <$> selectList [VerifKeySharedUsageUser ==. rsid] [Asc VerifKeySharedUsageId, LimitTo 1]
|
||||||
(count [VerifKeySharedUsageUser ==. rsid])
|
|
||||||
(count [VerifKeySharer ==. Just rsid])
|
getOldPersonalKeyId rsid = fmap entityKey . listToMaybe <$> selectList [VerifKeySharer ==. Just rsid] [Asc VerifKeyExpires, Asc VerifKeyId, LimitTo 1]
|
||||||
|
|
||||||
|
makeActorRoomByPersonal limit rsid vkid = do
|
||||||
|
room <-
|
||||||
|
if limit <= 1
|
||||||
|
then return False
|
||||||
|
else (< limit-1) <$> count [VerifKeySharer ==. Just rsid, VerifKeyId !=. vkid]
|
||||||
|
unless room $ delete vkid
|
||||||
|
|
||||||
|
makeActorRoomByUsage limit rsid suid = do
|
||||||
|
room <-
|
||||||
|
if limit <= 1
|
||||||
|
then return False
|
||||||
|
else
|
||||||
|
sumUpTo (limit-1)
|
||||||
|
(count [VerifKeySharedUsageUser ==. rsid, VerifKeySharedUsageId !=. suid])
|
||||||
|
(count [VerifKeySharer ==. Just rsid])
|
||||||
|
unless room $ delete suid
|
||||||
|
|
||||||
|
-- | Checks whether the given actor has room left for a new shared key usage
|
||||||
|
-- record, and if not, deletes a record to make room for a new one. It prefers
|
||||||
|
-- to delete a usage record if any exist; otherwise it deletes a personal key.
|
||||||
|
--
|
||||||
|
-- The first parameter is the actor key storage limit, and it must be above
|
||||||
|
-- zero.
|
||||||
|
makeActorRoomForUsage :: Int -> RemoteSharerId -> AppDB ()
|
||||||
|
makeActorRoomForUsage limit rsid = do
|
||||||
|
msuid <- getOldUsageId rsid
|
||||||
|
case msuid of
|
||||||
|
Nothing -> do
|
||||||
|
mvkid <- getOldPersonalKeyId rsid
|
||||||
|
case mvkid of
|
||||||
|
Nothing -> return ()
|
||||||
|
Just vkid -> makeActorRoomByPersonal limit rsid vkid
|
||||||
|
Just suid -> makeActorRoomByUsage limit rsid suid
|
||||||
|
|
||||||
|
-- | Checks whether the given actor has room left for a new personal key
|
||||||
|
-- record, and if not, deletes a record to make room for a new one. It prefers
|
||||||
|
-- to delete a personal key if any exist; otherwise it deletes a usage record.
|
||||||
|
--
|
||||||
|
-- The first parameter is the actor key storage limit, and it must be above
|
||||||
|
-- zero.
|
||||||
|
makeActorRoomForPersonalKey :: Int -> RemoteSharerId -> AppDB ()
|
||||||
|
makeActorRoomForPersonalKey limit rsid = do
|
||||||
|
mvkid <- getOldPersonalKeyId rsid
|
||||||
|
case mvkid of
|
||||||
|
Nothing -> do
|
||||||
|
msuid <- getOldUsageId rsid
|
||||||
|
case msuid of
|
||||||
|
Nothing -> return ()
|
||||||
|
Just suid -> makeActorRoomByUsage limit rsid suid
|
||||||
|
Just vkid -> makeActorRoomByPersonal limit rsid vkid
|
||||||
|
|
||||||
|
-- | Checks whether the given instance has room left for a new shared key
|
||||||
|
-- record, and if not, deletes a record to make room for a new one.
|
||||||
|
--
|
||||||
|
-- The first parameter is the actor key storage limit, and it must be above
|
||||||
|
-- zero.
|
||||||
|
makeInstanceRoom :: Int -> InstanceId -> AppDB ()
|
||||||
|
makeInstanceRoom limit iid = do
|
||||||
|
mvk <- listToMaybe <$> selectList [VerifKeyInstance ==. iid, VerifKeySharer ==. Nothing] [Asc VerifKeyExpires, Asc VerifKeyId, LimitTo 1]
|
||||||
|
case mvk of
|
||||||
|
Nothing -> return ()
|
||||||
|
Just (Entity vkid _) -> do
|
||||||
|
room <-
|
||||||
|
if limit <= 1
|
||||||
|
then return False
|
||||||
|
else (< limit-1) <$> count [VerifKeyInstance ==. iid, VerifKeySharer ==. Nothing, VerifKeyId !=. vkid]
|
||||||
|
unless room $ delete vkid
|
||||||
|
|
||||||
|
data RoomModeDB
|
||||||
|
= RoomModeNoLimit
|
||||||
|
| RoomModeLimit Int
|
||||||
|
|
||||||
|
data RoomMode
|
||||||
|
= RoomModeInstant
|
||||||
|
| RoomModeCached RoomModeDB
|
||||||
|
|
||||||
|
roomModeFromLimit :: Maybe Int -> RoomMode
|
||||||
|
roomModeFromLimit Nothing = RoomModeCached $ RoomModeNoLimit
|
||||||
|
roomModeFromLimit (Just limit) =
|
||||||
|
if limit <= 0
|
||||||
|
then RoomModeInstant
|
||||||
|
else RoomModeCached $ RoomModeLimit limit
|
||||||
|
|
||||||
|
actorRoomMode :: AppSettings -> RoomMode
|
||||||
|
actorRoomMode = roomModeFromLimit . appMaxActorKeys
|
||||||
|
|
||||||
|
instanceRoomMode :: AppSettings -> RoomMode
|
||||||
|
instanceRoomMode = roomModeFromLimit . appMaxInstanceKeys
|
||||||
|
|
||||||
-- | Given a shared key we have in our DB, verify that the given actor lists
|
-- | Given a shared key we have in our DB, verify that the given actor lists
|
||||||
-- this key, and update the DB accordingly.
|
-- this key, and update the DB accordingly.
|
||||||
--
|
--
|
||||||
-- * If we know the actor and we have a record that it lists the key, return
|
-- * If the storage limit on actor keys is zero:
|
||||||
-- success, otherwise no action
|
-- - If we're supposed to reject signatures when there's no room, raise
|
||||||
-- * If we know the actor but we don't have a record of usage, fetch the
|
-- an error! We can't store anything with a limit of 0
|
||||||
-- actor and verify usage. If the actor already has 2 known keys, return
|
-- - Otherwise, fetch the actor, store in DB if we don't have it, verify
|
||||||
-- error, otherwise store usage in DB.
|
-- usage via actor JSON. Usage isn't stored in the DB.
|
||||||
-- * If we don't know the actor, fetch actor, verify usage, store actor and
|
-- * If there's no storage limit, or it's above zero:
|
||||||
-- usage in DB.
|
-- - If we know the actor and we have a record that it lists the key,
|
||||||
|
-- return success, no other action
|
||||||
|
-- - If we know the actor but we don't have a record of usage, fetch the
|
||||||
|
-- actor and verify usage. If the actor already has the maximal number of
|
||||||
|
-- keys: If we're supposed to reject signatures when there's no room,
|
||||||
|
-- raise an error. Otherwise, delete an old key/usage and store the new
|
||||||
|
-- usage in the DB.
|
||||||
|
-- - If we don't know the actor, fetch actor, verify usage, store actor and
|
||||||
|
-- usage in DB.
|
||||||
--
|
--
|
||||||
-- If we get success, that means the actor lists the key, and both the actor
|
-- If we get success, that means the actor lists the key, and both the actor
|
||||||
-- and the usage exist in our DB now.
|
-- and the usage exist in our DB now (if the storage limit isn't zero).
|
||||||
keyListedByActorShared
|
keyListedByActorShared
|
||||||
:: Manager
|
:: Manager
|
||||||
-> InstanceId
|
-> InstanceId
|
||||||
|
@ -629,33 +726,46 @@ keyListedByActorShared
|
||||||
-> LocalURI
|
-> LocalURI
|
||||||
-> ExceptT String Handler ()
|
-> ExceptT String Handler ()
|
||||||
keyListedByActorShared manager iid vkid host luKey luActor = do
|
keyListedByActorShared manager iid vkid host luKey luActor = do
|
||||||
mresult <- do
|
(reject, roomMode) <- do
|
||||||
ments <- lift $ runDB $ do
|
s <- getsYesod appSettings
|
||||||
mrs <- getBy $ UniqueRemoteSharer iid luActor
|
return (appRejectOnMaxKeys s, actorRoomMode s)
|
||||||
for mrs $ \ (Entity rsid _) ->
|
case roomMode of
|
||||||
(rsid,) . isJust <$>
|
RoomModeInstant -> do
|
||||||
getBy (UniqueVerifKeySharedUsage vkid rsid)
|
when reject $ throwE "Actor key storage limit is 0 and set to reject"
|
||||||
return $
|
luInbox <- actorInbox <$> ExceptT (keyListedByActor manager host luKey luActor)
|
||||||
case ments of
|
_ <- lift $ runDB $ insertUnique $ RemoteSharer luActor iid luInbox
|
||||||
Nothing -> Just Nothing
|
return ()
|
||||||
Just (rsid, used) ->
|
RoomModeCached m -> do
|
||||||
if used
|
mresult <- do
|
||||||
then Nothing
|
ments <- lift $ runDB $ do
|
||||||
else Just $ Just rsid
|
mrs <- getBy $ UniqueRemoteSharer iid luActor
|
||||||
for_ mresult $ \ mrsid -> do
|
for mrs $ \ (Entity rsid _) ->
|
||||||
luInbox <- actorInbox <$> ExceptT (keyListedByActor manager host luKey luActor)
|
(rsid,) . isJust <$>
|
||||||
ExceptT $ runDB $ case mrsid of
|
getBy (UniqueVerifKeySharedUsage vkid rsid)
|
||||||
Nothing -> do
|
return $
|
||||||
rsid <- insert $ RemoteSharer luActor iid luInbox
|
case ments of
|
||||||
insert_ $ VerifKeySharedUsage vkid rsid
|
Nothing -> Just Nothing
|
||||||
return $ Right ()
|
Just (rsid, used) ->
|
||||||
Just rsid -> do
|
if used
|
||||||
room <- actorRoom rsid
|
then Nothing
|
||||||
if room
|
else Just $ Just rsid
|
||||||
then do
|
for_ mresult $ \ mrsid -> do
|
||||||
|
luInbox <- actorInbox <$> ExceptT (keyListedByActor manager host luKey luActor)
|
||||||
|
ExceptT $ runDB $ case mrsid of
|
||||||
|
Nothing -> do
|
||||||
|
rsid <- insert $ RemoteSharer luActor iid luInbox
|
||||||
insert_ $ VerifKeySharedUsage vkid rsid
|
insert_ $ VerifKeySharedUsage vkid rsid
|
||||||
return $ Right ()
|
return $ Right ()
|
||||||
else return $ Left "Actor already has at least 2 keys"
|
Just rsid -> runExceptT $ do
|
||||||
|
case m of
|
||||||
|
RoomModeNoLimit -> return ()
|
||||||
|
RoomModeLimit limit -> do
|
||||||
|
if reject
|
||||||
|
then do
|
||||||
|
room <- lift $ actorRoom limit rsid
|
||||||
|
unless room $ throwE "Actor key storage limit reached"
|
||||||
|
else lift $ makeActorRoomForUsage limit rsid
|
||||||
|
lift $ insert_ $ VerifKeySharedUsage vkid rsid
|
||||||
|
|
||||||
data VerifKeyDetail = VerifKeyDetail
|
data VerifKeyDetail = VerifKeyDetail
|
||||||
{ vkdKeyId :: LocalURI
|
{ vkdKeyId :: LocalURI
|
||||||
|
@ -792,42 +902,58 @@ instance YesodHttpSig App where
|
||||||
then addSharedKey h uinb vkd
|
then addSharedKey h uinb vkd
|
||||||
else addPersonalKey h uinb vkd
|
else addPersonalKey h uinb vkd
|
||||||
where
|
where
|
||||||
addSharedKey host luInbox (VerifKeyDetail luKey key mexpires luActor _) = do
|
addSharedKey host luInbox (VerifKeyDetail luKey key mexpires luActor _) = runExceptT $ do
|
||||||
(iid, rsid, inew) <- instanceAndActor host luActor luInbox
|
(reject, roomModeA, roomModeI) <- do
|
||||||
room <-
|
s <- getsYesod appSettings
|
||||||
case inew of
|
return (appRejectOnMaxKeys s, actorRoomMode s, instanceRoomMode s)
|
||||||
Nothing -> pure True
|
(iid, rsid, inew) <- lift $ instanceAndActor host luActor luInbox
|
||||||
Just rsnew -> do
|
case roomModeI of
|
||||||
iRoom <- instanceRoom iid
|
RoomModeInstant ->
|
||||||
if iRoom
|
when reject $ throwE "Instance key storage limit is 0 and set to reject"
|
||||||
then if rsnew
|
RoomModeCached m -> do
|
||||||
then pure True
|
case m of
|
||||||
else actorRoom rsid
|
RoomModeNoLimit -> return ()
|
||||||
else return False
|
RoomModeLimit limit ->
|
||||||
if room
|
if reject
|
||||||
then do
|
then when (isJust inew) $ do
|
||||||
vkid <- insert $ VerifKey luKey iid mexpires key Nothing
|
room <- lift $ instanceRoom limit iid
|
||||||
insert_ $ VerifKeySharedUsage vkid rsid
|
unless room $ throwE "Instance key storage limit reached"
|
||||||
return $ Right ()
|
else when (isJust inew) $ lift $ makeInstanceRoom limit iid
|
||||||
else return $ Left "We've reached key storage limit"
|
vkid <- lift $ insert $ VerifKey luKey iid mexpires key Nothing
|
||||||
|
case roomModeA of
|
||||||
|
RoomModeInstant ->
|
||||||
|
when reject $ throwE "Actor key storage limit is 0 and set to reject"
|
||||||
|
RoomModeCached m -> do
|
||||||
|
case m of
|
||||||
|
RoomModeNoLimit -> return ()
|
||||||
|
RoomModeLimit limit ->
|
||||||
|
if reject
|
||||||
|
then when (inew == Just False) $ do
|
||||||
|
room <- lift $ actorRoom limit rsid
|
||||||
|
unless room $ throwE "Actor key storage limit reached"
|
||||||
|
else when (inew == Just False) $ lift $ makeActorRoomForUsage limit rsid
|
||||||
|
lift $ insert_ $ VerifKeySharedUsage vkid rsid
|
||||||
where
|
where
|
||||||
instanceRoom iid = do
|
instanceRoom n iid =
|
||||||
mn <- getsYesod $ appMaxInstanceKeys . appSettings
|
(< n) <$> count [VerifKeyInstance ==. iid, VerifKeySharer ==. Nothing]
|
||||||
case mn of
|
addPersonalKey host luInbox (VerifKeyDetail luKey key mexpires luActor _) = runExceptT $ do
|
||||||
Nothing -> pure True
|
(reject, roomMode) <- do
|
||||||
Just n ->
|
s <- getsYesod appSettings
|
||||||
(< n) <$> count [VerifKeyInstance ==. iid, VerifKeySharer ==. Nothing]
|
return (appRejectOnMaxKeys s, actorRoomMode s)
|
||||||
addPersonalKey host luInbox (VerifKeyDetail luKey key mexpires luActor _) = do
|
(iid, rsid, inew) <- lift $ instanceAndActor host luActor luInbox
|
||||||
(iid, rsid, inew) <- instanceAndActor host luActor luInbox
|
case roomMode of
|
||||||
room <-
|
RoomModeInstant ->
|
||||||
if inew == Just False
|
when reject $ throwE "Actor key storage limit is 0 and set to reject"
|
||||||
then actorRoom rsid
|
RoomModeCached m -> do
|
||||||
else pure True
|
case m of
|
||||||
if room
|
RoomModeNoLimit -> return ()
|
||||||
then do
|
RoomModeLimit limit ->
|
||||||
insert_ $ VerifKey luKey iid mexpires key (Just rsid)
|
if reject
|
||||||
return $ Right ()
|
then when (inew == Just False) $ do
|
||||||
else return $ Left "We've reached key storage limit"
|
room <- lift $ actorRoom limit rsid
|
||||||
|
unless room $ throwE "Actor key storage limit reached"
|
||||||
|
else when (inew == Just False) $ lift $ makeActorRoomForPersonalKey limit rsid
|
||||||
|
lift $ insert_ $ VerifKey luKey iid mexpires key (Just rsid)
|
||||||
updateVerifKey vkid vkd =
|
updateVerifKey vkid vkd =
|
||||||
update vkid [VerifKeyExpires =. vkdExpires vkd, VerifKeyPublic =. vkdKey vkd]
|
update vkid [VerifKeyExpires =. vkdExpires vkd, VerifKeyPublic =. vkdKey vkd]
|
||||||
|
|
||||||
|
|
|
@ -109,10 +109,25 @@ data AppSettings = AppSettings
|
||||||
-- details. If set to 'Nothing', no email will be sent.
|
-- details. If set to 'Nothing', no email will be sent.
|
||||||
, appMail :: Maybe MailSettings
|
, appMail :: Maybe MailSettings
|
||||||
|
|
||||||
-- Signing key file for signing object capabilities sent to remote users
|
-- | Signing key file for signing object capabilities sent to remote
|
||||||
|
-- users
|
||||||
, appCapabilitySigningKeyFile :: FilePath
|
, appCapabilitySigningKeyFile :: FilePath
|
||||||
-- Salt for encoding and decoding hashids
|
-- | Salt for encoding and decoding hashids
|
||||||
, appHashidsSaltFile :: FilePath
|
, appHashidsSaltFile :: FilePath
|
||||||
|
-- | What do to when we wish to insert a new 'VerifKey' or
|
||||||
|
-- 'VerifKeySharedUsage' into the database, but we've reached the
|
||||||
|
-- configured storage limit.
|
||||||
|
--
|
||||||
|
-- 'True' means we simply reject HTTP signatures when it happens, which
|
||||||
|
-- means we basically don't support servers that use more keys or custom
|
||||||
|
-- setup other than what Vervis does.
|
||||||
|
--
|
||||||
|
-- 'False' means we do accept HTTP signatures even if we've reached the
|
||||||
|
-- storage limit setting. We simply handle it by remembering only the
|
||||||
|
-- amount of keys the limit allows, and otherwise we have to refetch keys
|
||||||
|
-- over HTTP, which possibly means we have to do more HTTP key fetching,
|
||||||
|
-- and the target server gets a higher load of key fetch GET requests.
|
||||||
|
, appRejectOnMaxKeys :: Bool
|
||||||
}
|
}
|
||||||
|
|
||||||
instance FromJSON AppSettings where
|
instance FromJSON AppSettings where
|
||||||
|
@ -156,6 +171,7 @@ instance FromJSON AppSettings where
|
||||||
|
|
||||||
appCapabilitySigningKeyFile <- o .: "capability-signing-key"
|
appCapabilitySigningKeyFile <- o .: "capability-signing-key"
|
||||||
appHashidsSaltFile <- o .: "hashids-salt-file"
|
appHashidsSaltFile <- o .: "hashids-salt-file"
|
||||||
|
appRejectOnMaxKeys <- o .: "reject-on-max-keys"
|
||||||
|
|
||||||
return AppSettings {..}
|
return AppSettings {..}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue