From 457230c299b3f61136856d1b454f0fbd24f11229 Mon Sep 17 00:00:00 2001 From: Stefan Matting Date: Tue, 15 Aug 2023 18:36:57 +0200 Subject: [PATCH 01/17] Refactor --- services/galley/src/Galley/API/Update.hs | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/services/galley/src/Galley/API/Update.hs b/services/galley/src/Galley/API/Update.hs index eb3253d10a3..6d54dfa6ac0 100644 --- a/services/galley/src/Galley/API/Update.hs +++ b/services/galley/src/Galley/API/Update.hs @@ -555,27 +555,23 @@ addCode lusr mZcon lcnv mReq = do Query.ensureConvAdmin (Data.convLocalMembers conv) (tUnqualified lusr) ensureAccess conv CodeAccess ensureGuestsOrNonTeamMembersAllowed conv - let (bots, users) = localBotsAndUsers $ Data.convLocalMembers conv + convUri <- E.getConversationCodeURI key <- E.makeKey (tUnqualified lcnv) - mCode <- E.getCode key ReusableCode - case mCode of + E.getCode key ReusableCode >>= \case Nothing -> do code <- E.generateCode (tUnqualified lcnv) ReusableCode (Timeout 3600 * 24 * 365) -- one year FUTUREWORK: configurable - mPw <- forM ((.password) =<< mReq) mkSafePassword + mPw <- for (mReq >>= (.password)) mkSafePassword E.createCode code mPw now <- input - conversationCode <- createCode (isJust mPw) code - let event = Event (tUntagged lcnv) Nothing (tUntagged lusr) now (EdConvCodeUpdate conversationCode) + let event = Event (tUntagged lcnv) Nothing (tUntagged lusr) now (EdConvCodeUpdate (mkConversationCodeInfo (isJust mPw) (codeKey code) (codeValue code) convUri)) + let (bots, users) = localBotsAndUsers $ Data.convLocalMembers conv pushConversationEvent mZcon event (qualifyAs lusr (map lmId users)) bots pure $ CodeAdded event + -- In case conversation already has a code this case covers the allowed no-ops Just (code, mPw) -> do when (isJust mPw || isJust (mReq >>= (.password))) $ throwS @'CreateConversationCodeConflict - conversationCode <- createCode (isJust mPw) code - pure $ CodeAlreadyExisted conversationCode + pure $ CodeAlreadyExisted (mkConversationCodeInfo (isJust mPw) (codeKey code) (codeValue code) convUri) where - createCode :: Bool -> Code -> Sem r ConversationCodeInfo - createCode hasPw code = do - mkConversationCodeInfo hasPw (codeKey code) (codeValue code) <$> E.getConversationCodeURI ensureGuestsOrNonTeamMembersAllowed :: Data.Conversation -> Sem r () ensureGuestsOrNonTeamMembersAllowed conv = unless From 0f1f7032f989fdd79ff926d60768812bdd900e96 Mon Sep 17 00:00:00 2001 From: Stefan Matting Date: Tue, 15 Aug 2023 20:24:24 +0200 Subject: [PATCH 02/17] Add test stub --- integration/test/API/Galley.hs | 15 +++++++++++++++ integration/test/Test/Conversation.hs | 18 ++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/integration/test/API/Galley.hs b/integration/test/API/Galley.hs index 967786f86c0..bf447a94d22 100644 --- a/integration/test/API/Galley.hs +++ b/integration/test/API/Galley.hs @@ -223,3 +223,18 @@ removeMember remover qcnv removed = do (removedDomain, removedId) <- objQid removed req <- baseRequest remover Galley Versioned (joinHttpPath ["conversations", convDomain, convId, "members", removedDomain, removedId]) submit "DELETE" req + +postConversationCode :: + (HasCallStack, MakesValue user, MakesValue conv) => + user -> + conv -> + Maybe String -> + App Response +postConversationCode user conv mbpassword = do + convId <- objId conv + req <- baseRequest user Galley Versioned (joinHttpPath ["conversations", convId, "code"]) + submit + "POST" + ( req + & addJSONObject ["password" .= pw | pw <- maybeToList mbpassword] + ) diff --git a/integration/test/Test/Conversation.hs b/integration/test/Test/Conversation.hs index ef1ef920c38..db899fe1b8d 100644 --- a/integration/test/Test/Conversation.hs +++ b/integration/test/Test/Conversation.hs @@ -462,3 +462,21 @@ testGetOneOnOneConvInStatusSentFromRemote = do filter ((==) d2ConvId) qConvIds `shouldMatch` [d2ConvId] resp <- getConversation d1User d2ConvId resp.status `shouldMatchInt` 200 + +testMultiIngressGuestLinks :: HasCallStack => App () +testMultiIngressGuestLinks = do + (user, _) <- createTeam OwnDomain + conv <- + postConversation + user + ( defProteus + { access = Just ["code"], + accessRole = Just ["team_member", "guest"] + } + ) + >>= getJSON 201 + res <- postConversationCode user conv Nothing >>= getJSON 201 + res %. "type" `shouldMatch` "conversation.code-update" + _uri <- res %. "data.uri" & asString + -- TODO: getConversationCode + printJSON res From 1547ba8228ac7bcad416e92fbe5be66a6d5c4473 Mon Sep 17 00:00:00 2001 From: Stefan Matting Date: Tue, 15 Aug 2023 21:35:07 +0200 Subject: [PATCH 03/17] wip --- .../API/Routes/Public/Galley/Conversation.hs | 2 ++ .../src/Galley/API/Public/Conversation.hs | 2 +- services/galley/src/Galley/API/Update.hs | 18 +++++++++++------- services/galley/src/Galley/App.hs | 14 +++++++++++--- services/galley/src/Galley/Cassandra/Code.hs | 8 +++++--- .../galley/src/Galley/Effects/CodeStore.hs | 2 +- services/galley/src/Galley/Env.hs | 5 +++-- services/galley/src/Galley/Options.hs | 19 +++++++++++++++++-- 8 files changed, 51 insertions(+), 19 deletions(-) diff --git a/libs/wire-api/src/Wire/API/Routes/Public/Galley/Conversation.hs b/libs/wire-api/src/Wire/API/Routes/Public/Galley/Conversation.hs index ce778e7d62e..9f6e9d13314 100644 --- a/libs/wire-api/src/Wire/API/Routes/Public/Galley/Conversation.hs +++ b/libs/wire-api/src/Wire/API/Routes/Public/Galley/Conversation.hs @@ -693,6 +693,7 @@ type ConversationAPI = :> CanThrow 'GuestLinksDisabled :> CanThrow 'CreateConversationCodeConflict :> ZUser + :> ZHostOpt :> ZOptConn :> "conversations" :> Capture' '[Description "Conversation ID"] "cnv" ConvId @@ -737,6 +738,7 @@ type ConversationAPI = :> CanThrow 'ConvAccessDenied :> CanThrow 'ConvNotFound :> CanThrow 'GuestLinksDisabled + :> ZHostOpt :> ZLocalUser :> "conversations" :> Capture' '[Description "Conversation ID"] "cnv" ConvId diff --git a/services/galley/src/Galley/API/Public/Conversation.hs b/services/galley/src/Galley/API/Public/Conversation.hs index 6ea0853f8ba..444f754adb1 100644 --- a/services/galley/src/Galley/API/Public/Conversation.hs +++ b/services/galley/src/Galley/API/Public/Conversation.hs @@ -58,7 +58,7 @@ conversationAPI = <@> mkNamedAPI @"join-conversation-by-id-unqualified" (callsFed joinConversationById) <@> mkNamedAPI @"join-conversation-by-code-unqualified" (callsFed joinConversationByReusableCode) <@> mkNamedAPI @"code-check" checkReusableCode - <@> mkNamedAPI @"create-conversation-code-unqualified@v3" (addCodeUnqualified Nothing) + <@> mkNamedAPI @"create-conversation-code-unqualified@v3" (addCodeUnqualified Nothing Nothing) <@> mkNamedAPI @"create-conversation-code-unqualified" addCodeUnqualifiedWithReqBody <@> mkNamedAPI @"get-conversation-guest-links-status" getConversationGuestLinksStatus <@> mkNamedAPI @"remove-code-unqualified" rmCodeUnqualified diff --git a/services/galley/src/Galley/API/Update.hs b/services/galley/src/Galley/API/Update.hs index 6d54dfa6ac0..d3215bdcdfb 100644 --- a/services/galley/src/Galley/API/Update.hs +++ b/services/galley/src/Galley/API/Update.hs @@ -497,11 +497,12 @@ addCodeUnqualifiedWithReqBody :: Member TeamFeatureStore r ) => UserId -> + Maybe Text -> Maybe ConnId -> ConvId -> CreateConversationCodeRequest -> Sem r AddCodeResult -addCodeUnqualifiedWithReqBody usr mZcon cnv req = addCodeUnqualified (Just req) usr mZcon cnv +addCodeUnqualifiedWithReqBody usr mbZHost mZcon cnv req = addCodeUnqualified (Just req) mbZHost usr mZcon cnv addCodeUnqualified :: forall r. @@ -520,14 +521,15 @@ addCodeUnqualified :: Member TeamFeatureStore r ) => Maybe CreateConversationCodeRequest -> + Maybe Text -> UserId -> Maybe ConnId -> ConvId -> Sem r AddCodeResult -addCodeUnqualified mReq usr mZcon cnv = do +addCodeUnqualified mReq mbZHost usr mZcon cnv = do lusr <- qualifyLocal usr lcnv <- qualifyLocal cnv - addCode lusr mZcon lcnv mReq + addCode lusr mbZHost mZcon lcnv mReq addCode :: forall r. @@ -545,17 +547,18 @@ addCode :: Member (Embed IO) r ) => Local UserId -> + Maybe Text -> Maybe ConnId -> Local ConvId -> Maybe CreateConversationCodeRequest -> Sem r AddCodeResult -addCode lusr mZcon lcnv mReq = do +addCode lusr mbZHost mZcon lcnv mReq = do conv <- E.getConversation (tUnqualified lcnv) >>= noteS @'ConvNotFound Query.ensureGuestLinksEnabled (Data.convTeam conv) Query.ensureConvAdmin (Data.convLocalMembers conv) (tUnqualified lusr) ensureAccess conv CodeAccess ensureGuestsOrNonTeamMembersAllowed conv - convUri <- E.getConversationCodeURI + convUri <- E.getConversationCodeURI mbZHost key <- E.makeKey (tUnqualified lcnv) E.getCode key ReusableCode >>= \case Nothing -> do @@ -635,10 +638,11 @@ getCode :: Member (Input Opts) r, Member TeamFeatureStore r ) => + Maybe Text -> Local UserId -> ConvId -> Sem r ConversationCodeInfo -getCode lusr cnv = do +getCode mbZHost lusr cnv = do conv <- E.getConversation cnv >>= noteS @'ConvNotFound Query.ensureGuestLinksEnabled (Data.convTeam conv) @@ -646,7 +650,7 @@ getCode lusr cnv = do ensureConvMember (Data.convLocalMembers conv) (tUnqualified lusr) key <- E.makeKey cnv (c, mPw) <- E.getCode key ReusableCode >>= noteS @'CodeNotFound - mkConversationCodeInfo (isJust mPw) (codeKey c) (codeValue c) <$> E.getConversationCodeURI + mkConversationCodeInfo (isJust mPw) (codeKey c) (codeValue c) <$> E.getConversationCodeURI mbZHost checkReusableCode :: forall r. diff --git a/services/galley/src/Galley/App.hs b/services/galley/src/Galley/App.hs index 55c1dfab8df..67d6157e942 100644 --- a/services/galley/src/Galley/App.hs +++ b/services/galley/src/Galley/App.hs @@ -21,8 +21,8 @@ module Galley.App ( -- * Environment Env, reqId, - monitor, options, + monitor, applog, manager, federator, @@ -53,6 +53,7 @@ import Control.Lens hiding ((.=)) import Data.Default (def) import Data.List.NonEmpty qualified as NE import Data.Metrics.Middleware +import Data.Misc import Data.Qualified import Data.Range import Data.Text (unpack) @@ -128,7 +129,7 @@ type GalleyEffects0 = type GalleyEffects = Append GalleyEffects1 GalleyEffects0 -- Define some invariants for the options used -validateOptions :: Opts -> IO () +validateOptions :: Opts -> IO (Either HttpsUrl (Map String HttpsUrl)) validateOptions o = do let settings' = view settings o optFanoutLimit = fromIntegral . fromRange $ currentFanoutLimit o @@ -140,19 +141,26 @@ validateOptions o = do (Nothing, Just _) -> error "RabbitMQ config is specified and federator is not, please specify both or none" (Just _, Nothing) -> error "Federator is specified and RabbitMQ config is not, please specify both or none" _ -> pure () + let errMsg = "Either setConversationCodeURI or setMultiIngress needs to be set." + case (settings' ^. conversationCodeURI, settings' ^. multiIngress) of + (Nothing, Nothing) -> error errMsg + (Nothing, Just mi) -> pure (Right mi) + (Just uri, Nothing) -> pure (Left uri) + (Just _, Just _) -> error errMsg createEnv :: Metrics -> Opts -> Logger -> IO Env createEnv m o l = do cass <- initCassandra o l mgr <- initHttpManager o h2mgr <- initHttp2Manager - validateOptions o + codeURIcfg <- validateOptions o Env def m o l mgr h2mgr (o ^. O.federator) (o ^. O.brig) cass <$> Q.new 16000 <*> initExtEnv <*> maybe (pure Nothing) (fmap Just . Aws.mkEnv l mgr) (o ^. journal) <*> loadAllMLSKeys (fold (o ^. settings . mlsPrivateKeyPaths)) <*> traverse (mkRabbitMqChannelMVar l) (o ^. rabbitmq) + <*> pure codeURIcfg initCassandra :: Opts -> Logger -> IO ClientState initCassandra o l = do diff --git a/services/galley/src/Galley/Cassandra/Code.hs b/services/galley/src/Galley/Cassandra/Code.hs index 9206425afe3..f93957c4f1d 100644 --- a/services/galley/src/Galley/Cassandra/Code.hs +++ b/services/galley/src/Galley/Cassandra/Code.hs @@ -29,7 +29,6 @@ import Galley.Data.Types import Galley.Data.Types qualified as Code import Galley.Effects.CodeStore (CodeStore (..)) import Galley.Env -import Galley.Options import Imports import Polysemy import Polysemy.Input @@ -48,8 +47,11 @@ interpretCodeStoreToCassandra = interpret $ \case DeleteCode k s -> embedClient $ deleteCode k s MakeKey cid -> Code.mkKey cid GenerateCode cid s t -> Code.generate cid s t - GetConversationCodeURI -> - view (options . settings . conversationCodeURI) <$> input + GetConversationCodeURI _mbHost -> do + env <- input + case env ^. convCodeURI of + Left _uri -> error "TODO" + Right _map -> error "TODO" -- | Insert a conversation code insertCode :: Code -> Maybe Password -> Client () diff --git a/services/galley/src/Galley/Effects/CodeStore.hs b/services/galley/src/Galley/Effects/CodeStore.hs index 88b31b0dfc1..092f9d9a965 100644 --- a/services/galley/src/Galley/Effects/CodeStore.hs +++ b/services/galley/src/Galley/Effects/CodeStore.hs @@ -53,6 +53,6 @@ data CodeStore m a where DeleteCode :: Key -> Scope -> CodeStore m () MakeKey :: ConvId -> CodeStore m Key GenerateCode :: ConvId -> Scope -> Timeout -> CodeStore m Code - GetConversationCodeURI :: CodeStore m HttpsUrl + GetConversationCodeURI :: Maybe Text -> CodeStore m HttpsUrl makeSem ''CodeStore diff --git a/services/galley/src/Galley/Env.hs b/services/galley/src/Galley/Env.hs index 32e45f2aa73..88858532a5c 100644 --- a/services/galley/src/Galley/Env.hs +++ b/services/galley/src/Galley/Env.hs @@ -25,7 +25,7 @@ import Control.Lens hiding ((.=)) import Data.ByteString.Conversion (toByteString') import Data.Id import Data.Metrics.Middleware -import Data.Misc (Fingerprint, Rsa) +import Data.Misc (Fingerprint, HttpsUrl, Rsa) import Data.Range import Galley.Aws qualified as Aws import Galley.Options @@ -63,7 +63,8 @@ data Env = Env _extEnv :: ExtEnv, _aEnv :: Maybe Aws.Env, _mlsKeys :: SignaturePurpose -> MLSKeys, - _rabbitmqChannel :: Maybe (MVar Q.Channel) + _rabbitmqChannel :: Maybe (MVar Q.Channel), + _convCodeURI :: Either HttpsUrl (Map String HttpsUrl) } -- | Environment specific to the communication with external diff --git a/services/galley/src/Galley/Options.hs b/services/galley/src/Galley/Options.hs index ab34df7f996..5c79359c0d7 100644 --- a/services/galley/src/Galley/Options.hs +++ b/services/galley/src/Galley/Options.hs @@ -22,16 +22,18 @@ module Galley.Options httpPoolSize, maxTeamSize, maxFanoutSize, - exposeInvitationURLsTeamAllowlist, maxConvSize, + exposeInvitationURLsTeamAllowlist, intraListing, disabledAPIVersions, conversationCodeURI, concurrentDeletionEvents, + deleteConvThrottleMillis, federationDomain, mlsPrivateKeyPaths, featureFlags, + multiIngress, defConcurrentDeletionEvents, defDeleteConvThrottleMillis, defFanoutLimit, @@ -90,7 +92,20 @@ data Settings = Settings -- | Whether to call Brig for device listing _intraListing :: !Bool, -- | URI prefix for conversations with access mode @code@ - _conversationCodeURI :: !HttpsUrl, + _conversationCodeURI :: !(Maybe HttpsUrl), + -- | Map from @Z-Host@ header to URI prefix for conversations with access mode @code@ + -- + -- If setMultiIngress is set then the URI prefix for guest links is looked + -- up in this config setting using the @Z-Host@ header value as a key. If + -- the lookup fails then no guest link can be created via the API. + -- + -- This option is only useful in the context of multi-ingress setups where + -- one backend / deployment is is reachable under several domains. + -- + -- setMultiIngress and setConversationCodeURI are mutually exclusive. One of + -- both options need to be configured. + -- _setMultiIngress :: Maybe (Map String HttpsUrl), + _multiIngress :: Maybe (Map String HttpsUrl), -- | Throttling: limits to concurrent deletion events _concurrentDeletionEvents :: !(Maybe Int), -- | Throttling: delay between sending events upon team deletion From d334a6e57bcf04fb9bd42de8e53e82df14cb3d52 Mon Sep 17 00:00:00 2001 From: Stefan Matting Date: Mon, 28 Aug 2023 17:36:48 +0200 Subject: [PATCH 04/17] wip2 --- integration/test/API/Galley.hs | 11 +++++- integration/test/Test/Conversation.hs | 39 ++++++++++++------- integration/test/Testlib/HTTP.hs | 12 +++--- libs/wire-api/src/Wire/API/Routes/Public.hs | 5 ++- services/galley/src/Galley/API/Update.hs | 21 ++++++++-- services/galley/src/Galley/App.hs | 4 +- services/galley/src/Galley/Cassandra/Code.hs | 10 +++-- .../galley/src/Galley/Effects/CodeStore.hs | 2 +- services/galley/src/Galley/Env.hs | 2 +- services/galley/src/Galley/Options.hs | 6 +-- 10 files changed, 75 insertions(+), 37 deletions(-) diff --git a/integration/test/API/Galley.hs b/integration/test/API/Galley.hs index bf447a94d22..c88bab45750 100644 --- a/integration/test/API/Galley.hs +++ b/integration/test/API/Galley.hs @@ -42,6 +42,13 @@ defProteus = defMLS :: CreateConv defMLS = defProteus {protocol = "mls"} +allowGuests :: CreateConv -> CreateConv +allowGuests cc = + cc + { access = Just ["code"], + accessRole = Just ["team_member", "guest"] + } + instance MakesValue CreateConv where make cc = do quids <- for (cc.qualifiedUsers) objQidObject @@ -229,12 +236,14 @@ postConversationCode :: user -> conv -> Maybe String -> + Maybe String -> App Response -postConversationCode user conv mbpassword = do +postConversationCode user conv mbpassword mbZHost = do convId <- objId conv req <- baseRequest user Galley Versioned (joinHttpPath ["conversations", convId, "code"]) submit "POST" ( req & addJSONObject ["password" .= pw | pw <- maybeToList mbpassword] + & maybe id zHost mbZHost ) diff --git a/integration/test/Test/Conversation.hs b/integration/test/Test/Conversation.hs index db899fe1b8d..3601a9d814a 100644 --- a/integration/test/Test/Conversation.hs +++ b/integration/test/Test/Conversation.hs @@ -465,18 +465,27 @@ testGetOneOnOneConvInStatusSentFromRemote = do testMultiIngressGuestLinks :: HasCallStack => App () testMultiIngressGuestLinks = do - (user, _) <- createTeam OwnDomain - conv <- - postConversation - user - ( defProteus - { access = Just ["code"], - accessRole = Just ["team_member", "guest"] - } - ) - >>= getJSON 201 - res <- postConversationCode user conv Nothing >>= getJSON 201 - res %. "type" `shouldMatch` "conversation.code-update" - _uri <- res %. "data.uri" & asString - -- TODO: getConversationCode - printJSON res + do + (user, _) <- createTeam OwnDomain + conv <- postConversation user (allowGuests defProteus) >>= getJSON 201 + res <- postConversationCode user conv Nothing Nothing >>= getJSON 201 + res %. "type" `shouldMatch` "conversation.code-update" + _uri <- res %. "data.uri" & asString + -- TODO: getConversationCode + printJSON res + + withModifiedBackend + ( def + { galleyCfg = + setField "settings.conversationCodeURI" (Null) + >=> setField "settings.multiIngress" (object ["red.example.com" .= "https://red.example.com"]) + } + ) + $ \domain -> do + (user, _) <- createTeam domain + conv <- postConversation user (allowGuests defProteus) >>= getJSON 201 + res <- postConversationCode user conv Nothing (Just "red.example.com") >>= getJSON 201 + res %. "type" `shouldMatch` "conversation.code-update" + _uri <- res %. "data.uri" & asString + -- TODO: getConversationCode + printJSON res diff --git a/integration/test/Testlib/HTTP.hs b/integration/test/Testlib/HTTP.hs index 1aa0b80ca75..670fbdc679e 100644 --- a/integration/test/Testlib/HTTP.hs +++ b/integration/test/Testlib/HTTP.hs @@ -78,12 +78,6 @@ addQueryParams :: [(String, String)] -> HTTP.Request -> HTTP.Request addQueryParams params req = HTTP.setQueryString (map (\(k, v) -> (cs k, Just (cs v))) params) req -zType :: String -> HTTP.Request -> HTTP.Request -zType = addHeader "Z-Type" - -zHost :: String -> HTTP.Request -> HTTP.Request -zHost = addHeader "Z-Host" - contentTypeJSON :: HTTP.Request -> HTTP.Request contentTypeJSON = addHeader "Content-Type" "application/json" @@ -156,6 +150,12 @@ zConnection = addHeader "Z-Connection" zClient :: String -> HTTP.Request -> HTTP.Request zClient = addHeader "Z-Client" +zType :: String -> HTTP.Request -> HTTP.Request +zType = addHeader "Z-Type" + +zHost :: String -> HTTP.Request -> HTTP.Request +zHost = addHeader "Z-Host" + submit :: String -> HTTP.Request -> App Response submit method req0 = do let req = req0 {HTTP.method = T.encodeUtf8 (T.pack method)} diff --git a/libs/wire-api/src/Wire/API/Routes/Public.hs b/libs/wire-api/src/Wire/API/Routes/Public.hs index 8ad302b6dd8..9d4e8eda43c 100644 --- a/libs/wire-api/src/Wire/API/Routes/Public.hs +++ b/libs/wire-api/src/Wire/API/Routes/Public.hs @@ -32,6 +32,7 @@ module Wire.API.Routes.Public ZProvider, DescriptionOAuthScope, ZHostOpt, + ZHostValue, ) where @@ -191,8 +192,10 @@ type ZOptConn = ZAuthServant 'ZAuthConn '[Servant.Optional, Servant.Strict] -- | Optional @Z-Host@ header (added by @nginz@) data ZHostOpt +type ZHostValue = Text + type ZOptHostHeader = - Header' '[Servant.Optional, Strict] "Z-Host" Text + Header' '[Servant.Optional, Strict] "Z-Host" ZHostValue instance HasSwagger api => HasSwagger (ZHostOpt :> api) where toSwagger _ = toSwagger (Proxy @api) diff --git a/services/galley/src/Galley/API/Update.hs b/services/galley/src/Galley/API/Update.hs index d3215bdcdfb..93f21ac6f18 100644 --- a/services/galley/src/Galley/API/Update.hs +++ b/services/galley/src/Galley/API/Update.hs @@ -79,6 +79,7 @@ import Data.Id import Data.Json.Util import Data.List1 import Data.Map.Strict qualified as Map +import Data.Misc (HttpsUrl) import Data.Qualified import Data.Set qualified as Set import Data.Singletons @@ -135,6 +136,7 @@ import Wire.API.Federation.Error import Wire.API.Message import Wire.API.Password (mkSafePassword) import Wire.API.Provider.Service (ServiceRef) +import Wire.API.Routes.Public (ZHostValue) import Wire.API.Routes.Public.Galley.Messaging import Wire.API.Routes.Public.Util (UpdateResult (..)) import Wire.API.ServantProto (RawProto (..)) @@ -558,7 +560,7 @@ addCode lusr mbZHost mZcon lcnv mReq = do Query.ensureConvAdmin (Data.convLocalMembers conv) (tUnqualified lusr) ensureAccess conv CodeAccess ensureGuestsOrNonTeamMembersAllowed conv - convUri <- E.getConversationCodeURI mbZHost + convUri <- getConversationCodeURI mbZHost key <- E.makeKey (tUnqualified lcnv) E.getCode key ReusableCode >>= \case Nothing -> do @@ -638,7 +640,7 @@ getCode :: Member (Input Opts) r, Member TeamFeatureStore r ) => - Maybe Text -> + Maybe ZHostValue -> Local UserId -> ConvId -> Sem r ConversationCodeInfo @@ -650,7 +652,8 @@ getCode mbZHost lusr cnv = do ensureConvMember (Data.convLocalMembers conv) (tUnqualified lusr) key <- E.makeKey cnv (c, mPw) <- E.getCode key ReusableCode >>= noteS @'CodeNotFound - mkConversationCodeInfo (isJust mPw) (codeKey c) (codeValue c) <$> E.getConversationCodeURI mbZHost + convUri <- getConversationCodeURI mbZHost + pure $ mkConversationCodeInfo (isJust mPw) (codeKey c) (codeValue c) convUri checkReusableCode :: forall r. @@ -1618,3 +1621,15 @@ rmBot lusr zcon b = do ensureConvMember :: (Member (ErrorS 'ConvNotFound) r) => [LocalMember] -> UserId -> Sem r () ensureConvMember users usr = unless (usr `isMember` users) $ throwS @'ConvNotFound + +getConversationCodeURI :: + ( Member (ErrorS 'ConvNotFound) r, + Member CodeStore r + ) => + Maybe ZHostValue -> + Sem r HttpsUrl +getConversationCodeURI mbZHost = do + mbURI <- E.getConversationCodeURI mbZHost + case mbURI of + Just uri -> pure uri + Nothing -> throwS @'ConvNotFound \ No newline at end of file diff --git a/services/galley/src/Galley/App.hs b/services/galley/src/Galley/App.hs index 67d6157e942..2163f9a8a50 100644 --- a/services/galley/src/Galley/App.hs +++ b/services/galley/src/Galley/App.hs @@ -129,7 +129,7 @@ type GalleyEffects0 = type GalleyEffects = Append GalleyEffects1 GalleyEffects0 -- Define some invariants for the options used -validateOptions :: Opts -> IO (Either HttpsUrl (Map String HttpsUrl)) +validateOptions :: Opts -> IO (Either HttpsUrl (Map Text HttpsUrl)) validateOptions o = do let settings' = view settings o optFanoutLimit = fromIntegral . fromRange $ currentFanoutLimit o @@ -141,7 +141,7 @@ validateOptions o = do (Nothing, Just _) -> error "RabbitMQ config is specified and federator is not, please specify both or none" (Just _, Nothing) -> error "Federator is specified and RabbitMQ config is not, please specify both or none" _ -> pure () - let errMsg = "Either setConversationCodeURI or setMultiIngress needs to be set." + let errMsg = "Either conversationCodeURI or multiIngress needs to be set." case (settings' ^. conversationCodeURI, settings' ^. multiIngress) of (Nothing, Nothing) -> error errMsg (Nothing, Just mi) -> pure (Right mi) diff --git a/services/galley/src/Galley/Cassandra/Code.hs b/services/galley/src/Galley/Cassandra/Code.hs index f93957c4f1d..f5b2770b38a 100644 --- a/services/galley/src/Galley/Cassandra/Code.hs +++ b/services/galley/src/Galley/Cassandra/Code.hs @@ -23,6 +23,7 @@ where import Cassandra import Control.Lens import Data.Code +import Data.Map qualified as Map import Galley.Cassandra.Queries qualified as Cql import Galley.Cassandra.Store import Galley.Data.Types @@ -47,11 +48,14 @@ interpretCodeStoreToCassandra = interpret $ \case DeleteCode k s -> embedClient $ deleteCode k s MakeKey cid -> Code.mkKey cid GenerateCode cid s t -> Code.generate cid s t - GetConversationCodeURI _mbHost -> do + GetConversationCodeURI mbHost -> do env <- input case env ^. convCodeURI of - Left _uri -> error "TODO" - Right _map -> error "TODO" + Left uri -> pure (Just uri) + Right map' -> + case mbHost of + Just host -> pure (Map.lookup host map') + Nothing -> pure Nothing -- | Insert a conversation code insertCode :: Code -> Maybe Password -> Client () diff --git a/services/galley/src/Galley/Effects/CodeStore.hs b/services/galley/src/Galley/Effects/CodeStore.hs index 092f9d9a965..15d71162f3b 100644 --- a/services/galley/src/Galley/Effects/CodeStore.hs +++ b/services/galley/src/Galley/Effects/CodeStore.hs @@ -53,6 +53,6 @@ data CodeStore m a where DeleteCode :: Key -> Scope -> CodeStore m () MakeKey :: ConvId -> CodeStore m Key GenerateCode :: ConvId -> Scope -> Timeout -> CodeStore m Code - GetConversationCodeURI :: Maybe Text -> CodeStore m HttpsUrl + GetConversationCodeURI :: Maybe Text -> CodeStore m (Maybe HttpsUrl) makeSem ''CodeStore diff --git a/services/galley/src/Galley/Env.hs b/services/galley/src/Galley/Env.hs index 88858532a5c..2bdb38c27ff 100644 --- a/services/galley/src/Galley/Env.hs +++ b/services/galley/src/Galley/Env.hs @@ -64,7 +64,7 @@ data Env = Env _aEnv :: Maybe Aws.Env, _mlsKeys :: SignaturePurpose -> MLSKeys, _rabbitmqChannel :: Maybe (MVar Q.Channel), - _convCodeURI :: Either HttpsUrl (Map String HttpsUrl) + _convCodeURI :: Either HttpsUrl (Map Text HttpsUrl) } -- | Environment specific to the communication with external diff --git a/services/galley/src/Galley/Options.hs b/services/galley/src/Galley/Options.hs index 5c79359c0d7..3ec6ba07668 100644 --- a/services/galley/src/Galley/Options.hs +++ b/services/galley/src/Galley/Options.hs @@ -28,7 +28,6 @@ module Galley.Options disabledAPIVersions, conversationCodeURI, concurrentDeletionEvents, - deleteConvThrottleMillis, federationDomain, mlsPrivateKeyPaths, @@ -102,10 +101,9 @@ data Settings = Settings -- This option is only useful in the context of multi-ingress setups where -- one backend / deployment is is reachable under several domains. -- - -- setMultiIngress and setConversationCodeURI are mutually exclusive. One of + -- multiIngress and conversationCodeURI are mutually exclusive. One of -- both options need to be configured. - -- _setMultiIngress :: Maybe (Map String HttpsUrl), - _multiIngress :: Maybe (Map String HttpsUrl), + _multiIngress :: Maybe (Map Text HttpsUrl), -- | Throttling: limits to concurrent deletion events _concurrentDeletionEvents :: !(Maybe Int), -- | Throttling: delay between sending events upon team deletion From 1c3206b93fb55776611ab18562819ccae569467b Mon Sep 17 00:00:00 2001 From: Stefan Matting Date: Tue, 29 Aug 2023 10:11:44 +0200 Subject: [PATCH 05/17] fix linting --- services/galley/src/Galley/API/Update.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/galley/src/Galley/API/Update.hs b/services/galley/src/Galley/API/Update.hs index 93f21ac6f18..9e0a09d6011 100644 --- a/services/galley/src/Galley/API/Update.hs +++ b/services/galley/src/Galley/API/Update.hs @@ -1632,4 +1632,4 @@ getConversationCodeURI mbZHost = do mbURI <- E.getConversationCodeURI mbZHost case mbURI of Just uri -> pure uri - Nothing -> throwS @'ConvNotFound \ No newline at end of file + Nothing -> throwS @'ConvNotFound From 20ed29a362b47c3659636e072bfd2045c5e7ef6b Mon Sep 17 00:00:00 2001 From: Stefan Matting Date: Tue, 29 Aug 2023 13:25:13 +0200 Subject: [PATCH 06/17] change response to access-denied --- services/galley/src/Galley/API/Update.hs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/services/galley/src/Galley/API/Update.hs b/services/galley/src/Galley/API/Update.hs index 9e0a09d6011..fbcdaaeddc1 100644 --- a/services/galley/src/Galley/API/Update.hs +++ b/services/galley/src/Galley/API/Update.hs @@ -1623,7 +1623,7 @@ ensureConvMember users usr = unless (usr `isMember` users) $ throwS @'ConvNotFound getConversationCodeURI :: - ( Member (ErrorS 'ConvNotFound) r, + ( Member (ErrorS 'ConvAccessDenied) r, Member CodeStore r ) => Maybe ZHostValue -> @@ -1632,4 +1632,4 @@ getConversationCodeURI mbZHost = do mbURI <- E.getConversationCodeURI mbZHost case mbURI of Just uri -> pure uri - Nothing -> throwS @'ConvNotFound + Nothing -> throwS @'ConvAccessDenied From a3e53e9f662cbc1625f42e4c1f76ae2c977f6011 Mon Sep 17 00:00:00 2001 From: Stefan Matting Date: Tue, 29 Aug 2023 13:25:33 +0200 Subject: [PATCH 07/17] Complete tests --- integration/test/API/Galley.hs | 16 +++++++ integration/test/Test/Conversation.hs | 66 +++++++++++++++++++++------ integration/test/Testlib/JSON.hs | 3 ++ 3 files changed, 72 insertions(+), 13 deletions(-) diff --git a/integration/test/API/Galley.hs b/integration/test/API/Galley.hs index c88bab45750..bb3aa2ac0d5 100644 --- a/integration/test/API/Galley.hs +++ b/integration/test/API/Galley.hs @@ -247,3 +247,19 @@ postConversationCode user conv mbpassword mbZHost = do & addJSONObject ["password" .= pw | pw <- maybeToList mbpassword] & maybe id zHost mbZHost ) + +getConversationCode :: + (HasCallStack, MakesValue user, MakesValue conv) => + user -> + conv -> + Maybe String -> + App Response +getConversationCode user conv mbZHost = do + convId <- objId conv + req <- baseRequest user Galley Versioned (joinHttpPath ["conversations", convId, "code"]) + submit + "GET" + ( req + & addQueryParams [("cnv", convId)] + & maybe id zHost mbZHost + ) diff --git a/integration/test/Test/Conversation.hs b/integration/test/Test/Conversation.hs index 3601a9d814a..f956480dc85 100644 --- a/integration/test/Test/Conversation.hs +++ b/integration/test/Test/Conversation.hs @@ -11,6 +11,7 @@ import API.Gundeck (getNotifications) import Control.Applicative import Control.Concurrent (threadDelay) import Data.Aeson qualified as Aeson +import Data.Text qualified as T import GHC.Stack import SetupHelpers import Testlib.One2One (generateRemoteAndConvIdWithDomain) @@ -466,26 +467,65 @@ testGetOneOnOneConvInStatusSentFromRemote = do testMultiIngressGuestLinks :: HasCallStack => App () testMultiIngressGuestLinks = do do + configuredURI <- readServiceConfig Galley & (%. "settings.conversationCodeURI") & asText + (user, _) <- createTeam OwnDomain conv <- postConversation user (allowGuests defProteus) >>= getJSON 201 - res <- postConversationCode user conv Nothing Nothing >>= getJSON 201 - res %. "type" `shouldMatch` "conversation.code-update" - _uri <- res %. "data.uri" & asString - -- TODO: getConversationCode - printJSON res + + bindResponse (postConversationCode user conv Nothing Nothing) $ \resp -> do + res <- getJSON 201 resp + res %. "type" `shouldMatch` "conversation.code-update" + guestLink <- res %. "data.uri" & asText + assertBool "guestlink incorrect" $ configuredURI `T.isPrefixOf` guestLink + + bindResponse (getConversationCode user conv Nothing) $ \resp -> do + res <- getJSON 200 resp + guestLink <- res %. "uri" & asText + assertBool "guestlink incorrect" $ configuredURI `T.isPrefixOf` guestLink + + bindResponse (getConversationCode user conv (Just "red.example.com")) $ \resp -> do + res <- getJSON 200 resp + guestLink <- res %. "uri" & asText + assertBool "guestlink incorrect" $ configuredURI `T.isPrefixOf` guestLink withModifiedBackend ( def - { galleyCfg = - setField "settings.conversationCodeURI" (Null) - >=> setField "settings.multiIngress" (object ["red.example.com" .= "https://red.example.com"]) + { galleyCfg = \conf -> + conf + & setField "settings.conversationCodeURI" Null + & setField + "settings.multiIngress" + ( object + [ "red.example.com" .= "https://red.example.com", + "blue.example.com" .= "https://blue.example.com" + ] + ) } ) $ \domain -> do (user, _) <- createTeam domain conv <- postConversation user (allowGuests defProteus) >>= getJSON 201 - res <- postConversationCode user conv Nothing (Just "red.example.com") >>= getJSON 201 - res %. "type" `shouldMatch` "conversation.code-update" - _uri <- res %. "data.uri" & asString - -- TODO: getConversationCode - printJSON res + + bindResponse (postConversationCode user conv Nothing (Just "red.example.com")) $ \resp -> do + res <- getJSON 201 resp + res %. "type" `shouldMatch` "conversation.code-update" + guestLink <- res %. "data.uri" & asText + assertBool "guestlink incorrect" $ (fromString "https://red.example.com") `T.isPrefixOf` guestLink + + bindResponse (getConversationCode user conv (Just "red.example.com")) $ \resp -> do + res <- getJSON 200 resp + guestLink <- res %. "uri" & asText + assertBool "guestlink incorrect" $ (fromString "https://red.example.com") `T.isPrefixOf` guestLink + + bindResponse (getConversationCode user conv (Just "blue.example.com")) $ \resp -> do + res <- getJSON 200 resp + guestLink <- res %. "uri" & asText + assertBool "guestlink incorrect" $ (fromString "https://blue.example.com") `T.isPrefixOf` guestLink + + bindResponse (getConversationCode user conv Nothing) $ \resp -> do + res <- getJSON 403 resp + res %. "label" `shouldMatch` "access-denied" + + bindResponse (getConversationCode user conv (Just "unknown.example.com")) $ \resp -> do + res <- getJSON 403 resp + res %. "label" `shouldMatch` "access-denied" diff --git a/integration/test/Testlib/JSON.hs b/integration/test/Testlib/JSON.hs index a5c932d8741..984a536ebe7 100644 --- a/integration/test/Testlib/JSON.hs +++ b/integration/test/Testlib/JSON.hs @@ -73,6 +73,9 @@ asString x = (String s) -> pure (T.unpack s) v -> assertFailureWithJSON x ("String" `typeWasExpectedButGot` v) +asText :: HasCallStack => MakesValue a => a -> App T.Text +asText = (fmap T.pack) . asString + asStringM :: HasCallStack => MakesValue a => a -> App (Maybe String) asStringM x = make x >>= \case From b3dd3f86fb3ff832e10ca855fa73777e971caa6a Mon Sep 17 00:00:00 2001 From: Stefan Matting Date: Tue, 29 Aug 2023 14:39:36 +0200 Subject: [PATCH 08/17] galley chart: make conversationCodeURI optional --- charts/galley/templates/configmap.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/charts/galley/templates/configmap.yaml b/charts/galley/templates/configmap.yaml index 10c22fafeb6..d3d6c300c72 100644 --- a/charts/galley/templates/configmap.yaml +++ b/charts/galley/templates/configmap.yaml @@ -59,7 +59,9 @@ data: {{- if .settings.exposeInvitationURLsTeamAllowlist }} exposeInvitationURLsTeamAllowlist: {{ .settings.exposeInvitationURLsTeamAllowlist }} {{- end }} + {{- if .settings.conversationCodeURI }} conversationCodeURI: {{ .settings.conversationCodeURI | quote }} + {{- end }} federationDomain: {{ .settings.federationDomain }} {{- if $.Values.secrets.mlsPrivateKeys }} mlsPrivateKeyPaths: From 78dd435124ccd4a723df2cce437c6a0669369c4a Mon Sep 17 00:00:00 2001 From: Stefan Matting Date: Tue, 29 Aug 2023 14:40:33 +0200 Subject: [PATCH 09/17] add TODO --- charts/galley/templates/configmap.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/charts/galley/templates/configmap.yaml b/charts/galley/templates/configmap.yaml index d3d6c300c72..6020dc1e5fc 100644 --- a/charts/galley/templates/configmap.yaml +++ b/charts/galley/templates/configmap.yaml @@ -62,6 +62,7 @@ data: {{- if .settings.conversationCodeURI }} conversationCodeURI: {{ .settings.conversationCodeURI | quote }} {{- end }} + # TODO: add multi ingress setting federationDomain: {{ .settings.federationDomain }} {{- if $.Values.secrets.mlsPrivateKeys }} mlsPrivateKeyPaths: From efb5313993f286c863b9514a615ab7c9ed1f99bc Mon Sep 17 00:00:00 2001 From: Stefan Matting Date: Tue, 29 Aug 2023 14:40:58 +0200 Subject: [PATCH 10/17] Docs: refactor: collate all multi-ingress configs --- .../src/developer/reference/config-options.md | 109 ++++++++---------- 1 file changed, 49 insertions(+), 60 deletions(-) diff --git a/docs/src/developer/reference/config-options.md b/docs/src/developer/reference/config-options.md index 3c681740f7b..a815ec684ec 100644 --- a/docs/src/developer/reference/config-options.md +++ b/docs/src/developer/reference/config-options.md @@ -657,39 +657,71 @@ The default setting is that no API version is disabled. ## Settings in cargohold -### (Fake) AWS - AWS S3 (or an alternative provider / service) is used to upload and download assets. The Haddock of [`CargoHold.Options.AWSOpts`](https://github.com/wireapp/wire-server/blob/develop/services/cargohold/src/CargoHold/Options.hs#L64) provides a lot of useful information. -#### Multi-Ingress setup + +## Multi-Ingress setup In a multi-ingress setup the backend is reachable via several domains, each handled by a separate Kubernetes ingress. This is useful to obfuscate the relationship of clients to each other, as an attacker on TCP/IP-level could only see domains and IPs that do not obviously relate to each other. +Each of these backend domains represents a virtual backend. N.B. these backend +domains are *DNS domains* only, not to be confused of the "backend domain" term used for federation (see {ref}`configure-federation`). In single-ingress setups the backend dns domain and federation backend domain is usually be the same, but this is not true for multi-ingress setups. + + +For a multi-ingress setup multiple services need to be configured: +### Nginz + +nginz sets [CORS +headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS). To generate +them for multiple domains (usually, *nginz* works with only one root domain) +these need to be defined with `nginx_conf.additional_external_env_domains`. + +E.g. + +```yaml +nginx_conf: + additional_external_env_domains: + - red.example.com + - green.example.org + - blue.example.net +``` + +### Cannon + +*cannon* sets [CORS +headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) for direct API +accesses by clients. To generate them for multiple domains (usually, *cannon* +works with only one root domain) these need to be defined with +`nginx_conf.additional_external_env_domains`. + +E.g. + +```yaml +nginx_conf: + additional_external_env_domains: + - red.example.com + - green.example.org + - blue.example.net +``` + +### Cargohold -In case of a fake AWS S3 service its identity needs to be obfuscated by making -it accessible via several domains, too. Thus, there isn't one -`s3DownloadEndpoint`, but one per domain at which the backend is reachable. Each -of these backend domains represents a virtual backend. N.B. these backend -domains are *DNS domains*. Do not confuse them with the federation domain! The -latter is just an identifier, and may or may not be equal to the backend's DNS -domain. Backend DNS domain(s) and federation domain are usually set equal by -convention. But, this is not true for multi-ingress setups! The backend domain of a download request is defined by its `Z-Host` header which -is set by `nginz`. (Multi-ingress handlling only applies to download requests as +is set by `nginz`. Multi-ingress handling only applies to download requests as these are implemented by redirects to the S3 assets host for local assets. -Uploads are handled by cargohold directly itself.) +Uploads are handled by cargohold directly itself. + -The config `aws.multiIngress` is a map from backend domain (`Z-Host` header -value) to a S3 download endpoint. The `Z-Host` header is set by `nginz` to the +For a multi-ingress setup `aws.multiIngress` needs to be configured as a map from backend domain (`Z-Host` header value) to a S3 download endpoint. The `Z-Host` header is set by `nginz` to the value of the incoming requests `Host` header. If there's no config map entry for a provided `Z-Host` in a download request for a local asset, then an error is -returned. +returned. When configured the configuration of `s3DownloadEndpoint` is ignored. This example shows a setup with fake backends *red*, *green* and *blue*: @@ -722,47 +754,4 @@ to the configuration example above: Unfortunately, kroki currently doesn't work on our CI: SQPIT-1810 Link to diagram: https://mermaid.live/edit#pako:eNrdVbFu2zAQ_ZUDJ7ewDdhtUkBDgBRB0CHIYCNL4eVEnmWiMk8lKbttkH8vJbsW5dCOUXSqBkHiPT6-e3yinoVkRSITEC5H32syku40FhbXCwP7C6VnC1hqSQNL6l1XeWRPwBuKqxk8OXKwpRyrahxGxvQD11VJY8mvSHPOB4UlMknSrtonbcfStBVar6Wu0HjQJgCdGwUNKfaonMGMax8WeH9acIq5FXKOuwVE7BcqN4U2v9IlibbgFZcqXZ5_ABeMxYK6uiXpwRb5YHp1NYTJ9FN7ixw3jW6ri5UHXva28rZ5BsVbUzIqB-gc-WgTD9DRzU3Pz7v9FChZYnk8L4KGiW23Gdyz3aJVQW7IoYvQbT3gDq2_wsIIbpWCr6MvHF5WhIpsL2p6g6HFhHePvdajFR6Yv0Fd7ZTDquF9mj3AMoR2t0zHcZg1CiJj92akdGP-OLBJ9JpDFOa73YGNxnRAFZ3Te9rxey5L3gZHdmueMrsLyBnHDwpScerGQr_9dn1tzfFeR_2k2MioRFIn15MhTD82Sb0-ndT4fPjM-emcdsDItf23eVlSW_D_ltXYv0uzenTknU_rOd_fzOsfy_9xYvtN_21ixVCsya5Rq_D3fG6KC-FXtKaFyMKjoiXWpV-IhXkJUKw9z38aKTJvaxqKulKBff-jFdkSS0cvvwHKl250 ---> - -## Settings in cannon - -### Multi-Ingress setup - -*cannon* sets [CORS -headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) for direct API -accesses by clients. To generate them for multiple domains (usually, *cannon* -works with only one root domain) these need to be defined with -`nginx_conf.additional_external_env_domains`. - -E.g. - -```yaml -nginx_conf: - additional_external_env_domains: - - red.example.com - - green.example.org - - blue.example.net -``` - -This setting has a dual in the *nginz* configuration. - -## Settings in nginz - -### Multi-Ingress setup - -nginz sets [CORS -headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS). To generate -them for multiple domains (usually, *nginz* works with only one root domain) -these need to be defined with `nginx_conf.additional_external_env_domains`. - -E.g. - -```yaml -nginx_conf: - additional_external_env_domains: - - red.example.com - - green.example.org - - blue.example.net -``` - -This setting has a dual in the *cannon* configuration. +--> \ No newline at end of file From 547f35b700b4010c9c9442f34d45ce4cb8b6437a Mon Sep 17 00:00:00 2001 From: Stefan Matting Date: Wed, 30 Aug 2023 10:48:58 +0200 Subject: [PATCH 11/17] Update helm chart templating (manually tested) --- charts/galley/templates/configmap.yaml | 7 +++++++ charts/galley/values.yaml | 14 ++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/charts/galley/templates/configmap.yaml b/charts/galley/templates/configmap.yaml index 6020dc1e5fc..e3fbb4a9ef5 100644 --- a/charts/galley/templates/configmap.yaml +++ b/charts/galley/templates/configmap.yaml @@ -61,6 +61,13 @@ data: {{- end }} {{- if .settings.conversationCodeURI }} conversationCodeURI: {{ .settings.conversationCodeURI | quote }} + {{- else if .settings.multiIngress }} + multiIngress: {{- toYaml .settings.multiIngress | nindent 8 }} + {{- else }} + {{ fail "Either settings.conversationCodeURI or settings.multiIngress have to be set"}} + {{- end }} + {{- if (and .settings.conversationCodeURI .settings.multiIngress) }} + {{ fail "settings.conversationCodeURI and settings.multiIngress are mutually exclusive" }} {{- end }} # TODO: add multi ingress setting federationDomain: {{ .settings.federationDomain }} diff --git a/charts/galley/values.yaml b/charts/galley/values.yaml index 875864142ff..5b328a3e5b1 100644 --- a/charts/galley/values.yaml +++ b/charts/galley/values.yaml @@ -34,6 +34,20 @@ config: exposeInvitationURLsTeamAllowlist: [] maxConvSize: 500 intraListing: true + # Either `conversationCodeURI` or `multiIngress` must be set + # + # `conversationCodeURI` is the URI prefix for conversation invitation links + # It should be of form https://{ACCOUNT_PAGES}/conversation-join/ + conversationCodeURI: null + # + # `multiIngress` is a `Z-Host` depended setting of conversationCodeURI. + # Use this only if you want to expose the instance on mutliple ingresses. + # If set it must a map from `Z-Host` to URI prefix + # Example: + # multiIngress: + # example.com: https://accounts.example.com/conversation-join/ + # example.net: https://accounts.example.net/conversation-join/ + multiIngress: null # Disable one ore more API versions. Please make sure the configuration value is the same in all these charts: # brig, cannon, cargohold, galley, gundeck, proxy, spar. # disabledAPIVersions: [ v3 ] From e6296449edba3e41c38f0b28d9dabeb8b88c8caa Mon Sep 17 00:00:00 2001 From: Stefan Matting Date: Wed, 30 Aug 2023 11:17:04 +0200 Subject: [PATCH 12/17] Update documentation --- docs/src/developer/reference/config-options.md | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/docs/src/developer/reference/config-options.md b/docs/src/developer/reference/config-options.md index a815ec684ec..e007c9ccdfa 100644 --- a/docs/src/developer/reference/config-options.md +++ b/docs/src/developer/reference/config-options.md @@ -7,8 +7,6 @@ Fragment. This page is about the yaml files that determine the configuration of the Wire backend services. -## Settings in galley - ### MLS private key paths Note: This developer documentation. Documentation for site operators can be found here: {ref}`mls-message-layer-security` @@ -754,4 +752,16 @@ to the configuration example above: Unfortunately, kroki currently doesn't work on our CI: SQPIT-1810 Link to diagram: https://mermaid.live/edit#pako:eNrdVbFu2zAQ_ZUDJ7ewDdhtUkBDgBRB0CHIYCNL4eVEnmWiMk8lKbttkH8vJbsW5dCOUXSqBkHiPT6-e3yinoVkRSITEC5H32syku40FhbXCwP7C6VnC1hqSQNL6l1XeWRPwBuKqxk8OXKwpRyrahxGxvQD11VJY8mvSHPOB4UlMknSrtonbcfStBVar6Wu0HjQJgCdGwUNKfaonMGMax8WeH9acIq5FXKOuwVE7BcqN4U2v9IlibbgFZcqXZ5_ABeMxYK6uiXpwRb5YHp1NYTJ9FN7ixw3jW6ri5UHXva28rZ5BsVbUzIqB-gc-WgTD9DRzU3Pz7v9FChZYnk8L4KGiW23Gdyz3aJVQW7IoYvQbT3gDq2_wsIIbpWCr6MvHF5WhIpsL2p6g6HFhHePvdajFR6Yv0Fd7ZTDquF9mj3AMoR2t0zHcZg1CiJj92akdGP-OLBJ9JpDFOa73YGNxnRAFZ3Te9rxey5L3gZHdmueMrsLyBnHDwpScerGQr_9dn1tzfFeR_2k2MioRFIn15MhTD82Sb0-ndT4fPjM-emcdsDItf23eVlSW_D_ltXYv0uzenTknU_rOd_fzOsfy_9xYvtN_21ixVCsya5Rq_D3fG6KC-FXtKaFyMKjoiXWpV-IhXkJUKw9z38aKTJvaxqKulKBff-jFdkSS0cvvwHKl250 ---> \ No newline at end of file +--> + +### Galley + +For conversation invite links to be correct in a multi-ingress setup `settings.multiIngress` needs to be configured as map from `Z-Host` to the conversation uri prefix. This setting is a `Z-Host` depended version of `settings.conversationCodeURI`. In fact `settings.multiIngress` and `settings.conversationCodeURI` are mutually exclusive. + +Example: + +```yaml +multiIngress: + red.example.com: https://accounts.red.example.com/conversation-join/ + green.example.com: https://accounts.green.example.net/conversation-join/ +``` \ No newline at end of file From 25850b33b2f7cfbd4c3bf1900b81b2f4cdd48e76 Mon Sep 17 00:00:00 2001 From: Stefan Matting Date: Wed, 30 Aug 2023 11:31:47 +0200 Subject: [PATCH 13/17] Carhohold: Fix incorrect inline documentation for multiIngress --- services/cargohold/src/CargoHold/Options.hs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/services/cargohold/src/CargoHold/Options.hs b/services/cargohold/src/CargoHold/Options.hs index 4f99c8300ad..e6180403032 100644 --- a/services/cargohold/src/CargoHold/Options.hs +++ b/services/cargohold/src/CargoHold/Options.hs @@ -21,7 +21,7 @@ module CargoHold.Options where import Amazonka (S3AddressingStyle (..)) -import qualified CargoHold.CloudFront as CF +import CargoHold.CloudFront qualified as CF import Control.Lens hiding (Level) import Data.Aeson (FromJSON (..), withText) import Data.Aeson.TH @@ -104,7 +104,7 @@ data AWSOpts = AWSOpts -- -- This logic is: If the @Z-Host@ header is provided and found in this map, -- the map's values is taken as s3 download endpoint to redirect to; - -- otherwise, `_awsS3DownloadEndpoint` is used. This option is only useful + -- otherwise an 302 error is thrown. This option is only useful -- in the context of multi-ingress setups where one backend / deployment is -- reachable under several domains. _multiIngress :: !(Maybe (Map String AWSEndpoint)) From 1de4a089b21b4f9aebdcba8040647a665fe8b3b3 Mon Sep 17 00:00:00 2001 From: Stefan Matting Date: Wed, 30 Aug 2023 11:47:29 +0200 Subject: [PATCH 14/17] Remove TODO --- charts/galley/templates/configmap.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/charts/galley/templates/configmap.yaml b/charts/galley/templates/configmap.yaml index e3fbb4a9ef5..e5afcb5f428 100644 --- a/charts/galley/templates/configmap.yaml +++ b/charts/galley/templates/configmap.yaml @@ -69,7 +69,6 @@ data: {{- if (and .settings.conversationCodeURI .settings.multiIngress) }} {{ fail "settings.conversationCodeURI and settings.multiIngress are mutually exclusive" }} {{- end }} - # TODO: add multi ingress setting federationDomain: {{ .settings.federationDomain }} {{- if $.Values.secrets.mlsPrivateKeys }} mlsPrivateKeyPaths: From afd404d61f7551376974fc3d7754b86f3ba3fcdf Mon Sep 17 00:00:00 2001 From: Stefan Matting Date: Wed, 30 Aug 2023 12:08:11 +0200 Subject: [PATCH 15/17] Fix incorrect qualified import --- services/cargohold/src/CargoHold/Options.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/cargohold/src/CargoHold/Options.hs b/services/cargohold/src/CargoHold/Options.hs index e6180403032..25339ccfa40 100644 --- a/services/cargohold/src/CargoHold/Options.hs +++ b/services/cargohold/src/CargoHold/Options.hs @@ -21,7 +21,7 @@ module CargoHold.Options where import Amazonka (S3AddressingStyle (..)) -import CargoHold.CloudFront qualified as CF +import qualified CargoHold.CloudFront as CF import Control.Lens hiding (Level) import Data.Aeson (FromJSON (..), withText) import Data.Aeson.TH From 3f07061d62965c1959d8014817e47dc8f13dc2d5 Mon Sep 17 00:00:00 2001 From: Stefan Matting Date: Thu, 31 Aug 2023 11:07:00 +0200 Subject: [PATCH 16/17] Make Z-Host an optional parameter in all API versions --- .../src/Wire/API/Routes/Public/Galley/Conversation.hs | 1 + services/galley/src/Galley/API/Public/Conversation.hs | 2 +- services/galley/src/Galley/API/Update.hs | 8 ++++---- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/libs/wire-api/src/Wire/API/Routes/Public/Galley/Conversation.hs b/libs/wire-api/src/Wire/API/Routes/Public/Galley/Conversation.hs index 9f6e9d13314..9b0b1250937 100644 --- a/libs/wire-api/src/Wire/API/Routes/Public/Galley/Conversation.hs +++ b/libs/wire-api/src/Wire/API/Routes/Public/Galley/Conversation.hs @@ -675,6 +675,7 @@ type ConversationAPI = :> CanThrow 'GuestLinksDisabled :> CanThrow 'CreateConversationCodeConflict :> ZUser + :> ZHostOpt :> ZOptConn :> "conversations" :> Capture' '[Description "Conversation ID"] "cnv" ConvId diff --git a/services/galley/src/Galley/API/Public/Conversation.hs b/services/galley/src/Galley/API/Public/Conversation.hs index 444f754adb1..6ea0853f8ba 100644 --- a/services/galley/src/Galley/API/Public/Conversation.hs +++ b/services/galley/src/Galley/API/Public/Conversation.hs @@ -58,7 +58,7 @@ conversationAPI = <@> mkNamedAPI @"join-conversation-by-id-unqualified" (callsFed joinConversationById) <@> mkNamedAPI @"join-conversation-by-code-unqualified" (callsFed joinConversationByReusableCode) <@> mkNamedAPI @"code-check" checkReusableCode - <@> mkNamedAPI @"create-conversation-code-unqualified@v3" (addCodeUnqualified Nothing Nothing) + <@> mkNamedAPI @"create-conversation-code-unqualified@v3" (addCodeUnqualified Nothing) <@> mkNamedAPI @"create-conversation-code-unqualified" addCodeUnqualifiedWithReqBody <@> mkNamedAPI @"get-conversation-guest-links-status" getConversationGuestLinksStatus <@> mkNamedAPI @"remove-code-unqualified" rmCodeUnqualified diff --git a/services/galley/src/Galley/API/Update.hs b/services/galley/src/Galley/API/Update.hs index fbcdaaeddc1..595a89d8858 100644 --- a/services/galley/src/Galley/API/Update.hs +++ b/services/galley/src/Galley/API/Update.hs @@ -504,7 +504,7 @@ addCodeUnqualifiedWithReqBody :: ConvId -> CreateConversationCodeRequest -> Sem r AddCodeResult -addCodeUnqualifiedWithReqBody usr mbZHost mZcon cnv req = addCodeUnqualified (Just req) mbZHost usr mZcon cnv +addCodeUnqualifiedWithReqBody usr mbZHost mZcon cnv req = addCodeUnqualified (Just req) usr mbZHost mZcon cnv addCodeUnqualified :: forall r. @@ -523,12 +523,12 @@ addCodeUnqualified :: Member TeamFeatureStore r ) => Maybe CreateConversationCodeRequest -> - Maybe Text -> UserId -> + Maybe ZHostValue -> Maybe ConnId -> ConvId -> Sem r AddCodeResult -addCodeUnqualified mReq mbZHost usr mZcon cnv = do +addCodeUnqualified mReq usr mbZHost mZcon cnv = do lusr <- qualifyLocal usr lcnv <- qualifyLocal cnv addCode lusr mbZHost mZcon lcnv mReq @@ -549,7 +549,7 @@ addCode :: Member (Embed IO) r ) => Local UserId -> - Maybe Text -> + Maybe ZHostValue -> Maybe ConnId -> Local ConvId -> Maybe CreateConversationCodeRequest -> From 3c27c2884ebeee9dc0c54dc26cc875a7dfa815a9 Mon Sep 17 00:00:00 2001 From: Stefan Matting Date: Thu, 31 Aug 2023 11:11:05 +0200 Subject: [PATCH 17/17] Update docs with suggestions from code review --- docs/src/developer/reference/config-options.md | 4 ++-- services/cargohold/src/CargoHold/Options.hs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/src/developer/reference/config-options.md b/docs/src/developer/reference/config-options.md index e007c9ccdfa..3d9dc14c4f4 100644 --- a/docs/src/developer/reference/config-options.md +++ b/docs/src/developer/reference/config-options.md @@ -668,7 +668,7 @@ handled by a separate Kubernetes ingress. This is useful to obfuscate the relationship of clients to each other, as an attacker on TCP/IP-level could only see domains and IPs that do not obviously relate to each other. Each of these backend domains represents a virtual backend. N.B. these backend -domains are *DNS domains* only, not to be confused of the "backend domain" term used for federation (see {ref}`configure-federation`). In single-ingress setups the backend dns domain and federation backend domain is usually be the same, but this is not true for multi-ingress setups. +domains are *DNS domains* only, not to be confused of the "backend domain" term used for federation (see {ref}`configure-federation`). In single-ingress setups the backend DNS domain and federation backend domain is usually be the same, but this is not true for multi-ingress setups. For a multi-ingress setup multiple services need to be configured: @@ -756,7 +756,7 @@ https://mermaid.live/edit#pako:eNrdVbFu2zAQ_ZUDJ7ewDdhtUkBDgBRB0CHIYCNL4eVEnmWiM ### Galley -For conversation invite links to be correct in a multi-ingress setup `settings.multiIngress` needs to be configured as map from `Z-Host` to the conversation uri prefix. This setting is a `Z-Host` depended version of `settings.conversationCodeURI`. In fact `settings.multiIngress` and `settings.conversationCodeURI` are mutually exclusive. +For conversation invite links to be correct in a multi-ingress setup `settings.multiIngress` needs to be configured as map from `Z-Host` to the conversation URI prefix. This setting is a `Z-Host` depended version of `settings.conversationCodeURI`. In fact `settings.multiIngress` and `settings.conversationCodeURI` are mutually exclusive. Example: diff --git a/services/cargohold/src/CargoHold/Options.hs b/services/cargohold/src/CargoHold/Options.hs index 25339ccfa40..aa515729a1f 100644 --- a/services/cargohold/src/CargoHold/Options.hs +++ b/services/cargohold/src/CargoHold/Options.hs @@ -104,7 +104,7 @@ data AWSOpts = AWSOpts -- -- This logic is: If the @Z-Host@ header is provided and found in this map, -- the map's values is taken as s3 download endpoint to redirect to; - -- otherwise an 302 error is thrown. This option is only useful + -- otherwise a 404 is retuned. This option is only useful -- in the context of multi-ingress setups where one backend / deployment is -- reachable under several domains. _multiIngress :: !(Maybe (Map String AWSEndpoint))