From 954aa31385ed6d58f09193c80798063914dfee3e Mon Sep 17 00:00:00 2001 From: Paolo Capriotti Date: Tue, 1 Oct 2024 15:05:10 +0200 Subject: [PATCH 1/7] Add option to disable federation for a protocol --- .../src/Wire/API/Federation/Error.hs | 10 ++++++++++ services/galley/src/Galley/API/Action.hs | 16 +++++++++++++++- services/galley/src/Galley/API/Create.hs | 1 + services/galley/src/Galley/Options.hs | 3 +++ 4 files changed, 29 insertions(+), 1 deletion(-) diff --git a/libs/wire-api-federation/src/Wire/API/Federation/Error.hs b/libs/wire-api-federation/src/Wire/API/Federation/Error.hs index 830a9f062fc..d13d2c23503 100644 --- a/libs/wire-api-federation/src/Wire/API/Federation/Error.hs +++ b/libs/wire-api-federation/src/Wire/API/Federation/Error.hs @@ -150,6 +150,8 @@ data FederationError | -- | No federator endpoint has been set, so no call to federator client can -- be made. FederationNotConfigured + | -- | Federation is disabled for the given protocol + FederationDisabledForProtocol | -- | An error occurred while invoking federator client (see -- 'FederatorClientError' for more details). FederationCallFailure FederatorClientError @@ -188,6 +190,7 @@ instance APIError FederationError where federationErrorToWai :: FederationError -> Wai.Error federationErrorToWai FederationNotImplemented = federationNotImplemented federationErrorToWai FederationNotConfigured = federationNotConfigured +federationErrorToWai FederationDisabledForProtocol = federationDisabledForProtocol federationErrorToWai (FederationCallFailure err) = federationClientErrorToWai err federationErrorToWai (FederationUnexpectedBody s) = federationUnexpectedBody s federationErrorToWai (FederationUnexpectedError t) = federationUnexpectedError t @@ -358,6 +361,13 @@ federationNotConfigured = "federation-not-enabled" "no federator configured" +federationDisabledForProtocol :: Wai.Error +federationDisabledForProtocol = + Wai.mkError + HTTP.status409 + "federation-disabled-for-protocol" + "Federation is disabled for the given protocol" + federationUnavailable :: Text -> Wai.Error federationUnavailable err = Wai.mkError diff --git a/services/galley/src/Galley/API/Action.hs b/services/galley/src/Galley/API/Action.hs index e96f060855e..9607a4b216d 100644 --- a/services/galley/src/Galley/API/Action.hs +++ b/services/galley/src/Galley/API/Action.hs @@ -39,6 +39,7 @@ module Galley.API.Action addLocalUsersToRemoteConv, ConversationUpdate, getFederationStatus, + enforceFederationProtocol, checkFederationStatus, firstConflictOrFullyConnected, ) @@ -121,7 +122,7 @@ import Wire.API.Team.Feature import Wire.API.Team.LegalHold import Wire.API.Team.Member import Wire.API.Team.Permission (Perm (AddRemoveConvMember, ModifyConvName)) -import Wire.API.User qualified as User +import Wire.API.User as User import Wire.NotificationSubsystem data NoChanges = NoChanges @@ -327,6 +328,19 @@ type family HasConversationActionGalleyErrors (tag :: ConversationActionTag) :: ErrorS 'TeamNotFound ] +enforceFederationProtocol :: + ( Member (Error FederationError) r, + Member (Input Opts) r + ) => + BaseProtocolTag -> + [Remote ()] -> + Sem r () +enforceFederationProtocol proto domains = do + unless (null domains) $ do + mAllowedProtos <- view (settings . federationProtocols) <$> input + unless (maybe True (elem proto) mAllowedProtos) $ + throw FederationDisabledForProtocol + checkFederationStatus :: ( Member (Error UnreachableBackends) r, Member (Error NonFederatingBackends) r, diff --git a/services/galley/src/Galley/API/Create.hs b/services/galley/src/Galley/API/Create.hs index 4b71e3fcf85..e99410181eb 100644 --- a/services/galley/src/Galley/API/Create.hs +++ b/services/galley/src/Galley/API/Create.hs @@ -160,6 +160,7 @@ createGroupConversation :: Sem r CreateGroupConversationResponse createGroupConversation lusr conn newConv = do let remoteDomains = void <$> snd (partitionQualified lusr $ newConv.newConvQualifiedUsers) + enforceFederationProtocol newConv.newConvProtocol remoteDomains checkFederationStatus (RemoteDomains $ Set.fromList remoteDomains) cnv <- createGroupConversationGeneric diff --git a/services/galley/src/Galley/Options.hs b/services/galley/src/Galley/Options.hs index be813e6ee3d..231b5b999f0 100644 --- a/services/galley/src/Galley/Options.hs +++ b/services/galley/src/Galley/Options.hs @@ -31,6 +31,7 @@ module Galley.Options concurrentDeletionEvents, deleteConvThrottleMillis, federationDomain, + federationProtocols, mlsPrivateKeyPaths, featureFlags, defConcurrentDeletionEvents, @@ -74,6 +75,7 @@ import Util.Options hiding (endpoint) import Util.Options.Common import Wire.API.Routes.Version import Wire.API.Team.Member +import Wire.API.User newtype GuestLinkTTLSeconds = GuestLinkTTLSeconds { unGuestLinkTTLSeconds :: Int @@ -136,6 +138,7 @@ data Settings = Settings -- - wire.com -- - example.com _federationDomain :: !Domain, + _federationProtocols :: !(Maybe [BaseProtocolTag]), _mlsPrivateKeyPaths :: !(Maybe MLSPrivateKeyPaths), -- | FUTUREWORK: 'setFeatureFlags' should be renamed to 'setFeatureConfigs' in all types. _featureFlags :: !FeatureFlags, From a6a43fed3af043954620b05270c48cf15419690e Mon Sep 17 00:00:00 2001 From: Paolo Capriotti Date: Tue, 1 Oct 2024 15:45:17 +0200 Subject: [PATCH 2/7] Enforce federation protocol when adding users --- services/galley/src/Galley/API/Action.hs | 3 ++- services/galley/src/Galley/API/Create.hs | 2 +- services/galley/src/Galley/Options.hs | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/services/galley/src/Galley/API/Action.hs b/services/galley/src/Galley/API/Action.hs index 9607a4b216d..e5e7ff6a79d 100644 --- a/services/galley/src/Galley/API/Action.hs +++ b/services/galley/src/Galley/API/Action.hs @@ -332,7 +332,7 @@ enforceFederationProtocol :: ( Member (Error FederationError) r, Member (Input Opts) r ) => - BaseProtocolTag -> + ProtocolTag -> [Remote ()] -> Sem r () enforceFederationProtocol proto domains = do @@ -535,6 +535,7 @@ performConversationJoin qusr lconv (ConversationJoin invited role) = do ensureMemberLimit (convProtocolTag conv) (toList (convLocalMembers conv)) newMembers ensureAccess conv InviteAccess checkLocals lusr (convTeam conv) (ulLocals newMembers) + enforceFederationProtocol (protocolTag conv.convProtocol) (fmap void (ulRemotes newMembers)) checkRemotes lusr (ulRemotes newMembers) checkLHPolicyConflictsLocal (ulLocals newMembers) checkLHPolicyConflictsRemote (FutureWork (ulRemotes newMembers)) diff --git a/services/galley/src/Galley/API/Create.hs b/services/galley/src/Galley/API/Create.hs index e99410181eb..9ab84a07469 100644 --- a/services/galley/src/Galley/API/Create.hs +++ b/services/galley/src/Galley/API/Create.hs @@ -160,7 +160,7 @@ createGroupConversation :: Sem r CreateGroupConversationResponse createGroupConversation lusr conn newConv = do let remoteDomains = void <$> snd (partitionQualified lusr $ newConv.newConvQualifiedUsers) - enforceFederationProtocol newConv.newConvProtocol remoteDomains + enforceFederationProtocol (baseProtocolToProtocol newConv.newConvProtocol) remoteDomains checkFederationStatus (RemoteDomains $ Set.fromList remoteDomains) cnv <- createGroupConversationGeneric diff --git a/services/galley/src/Galley/Options.hs b/services/galley/src/Galley/Options.hs index 231b5b999f0..1f916761387 100644 --- a/services/galley/src/Galley/Options.hs +++ b/services/galley/src/Galley/Options.hs @@ -73,9 +73,9 @@ import Network.AMQP.Extended import System.Logger.Extended (Level, LogFormat) import Util.Options hiding (endpoint) import Util.Options.Common +import Wire.API.Conversation.Protocol import Wire.API.Routes.Version import Wire.API.Team.Member -import Wire.API.User newtype GuestLinkTTLSeconds = GuestLinkTTLSeconds { unGuestLinkTTLSeconds :: Int @@ -138,7 +138,7 @@ data Settings = Settings -- - wire.com -- - example.com _federationDomain :: !Domain, - _federationProtocols :: !(Maybe [BaseProtocolTag]), + _federationProtocols :: !(Maybe [ProtocolTag]), _mlsPrivateKeyPaths :: !(Maybe MLSPrivateKeyPaths), -- | FUTUREWORK: 'setFeatureFlags' should be renamed to 'setFeatureConfigs' in all types. _featureFlags :: !FeatureFlags, From d004d8bb280bb6bd5e6bcd891ac3fb6cf6820ee8 Mon Sep 17 00:00:00 2001 From: Paolo Capriotti Date: Wed, 2 Oct 2024 09:05:59 +0200 Subject: [PATCH 3/7] Test disabling federation for proteus --- integration/test/Test/Conversation.hs | 23 ++++++++++++++++++++ services/galley/src/Galley/Types/UserList.hs | 2 +- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/integration/test/Test/Conversation.hs b/integration/test/Test/Conversation.hs index e6ae83d1519..0140b25afb2 100644 --- a/integration/test/Test/Conversation.hs +++ b/integration/test/Test/Conversation.hs @@ -908,3 +908,26 @@ testPostConvWithUnreachableRemoteUsers = do for_ convs $ \conv -> conv %. "type" `shouldNotMatchInt` 0 assertNoEvent 2 wssAlice assertNoEvent 2 wssAlex + +testNoFederationWithProteus :: (HasCallStack) => App () +testNoFederationWithProteus = do + withModifiedBackend + ( def + { galleyCfg = \conf -> + conf & setField "settings.federationProtocols" ["mls"] + } + ) + $ \domain -> do + charlieDomain <- asString $ make OwnDomain + [alice, alex, arnold, bob] <- createAndConnectUsers [domain, domain, domain, charlieDomain] + + do + conv <- postConversation alice defProteus {qualifiedUsers = [alex]} >>= getJSON 201 + bindResponse (addMembers alice conv def {users = [bob]}) $ \resp -> do + resp.status `shouldMatchInt` 409 + resp.json %. "label" `shouldMatch` "federation-disabled-for-protocol" + void $ addMembers alice conv def {users = [arnold]} >>= getJSON 200 + + bindResponse (postConversation alice defProteus {qualifiedUsers = [bob]}) $ \resp -> do + resp.status `shouldMatchInt` 409 + resp.json %. "label" `shouldMatch` "federation-disabled-for-protocol" diff --git a/services/galley/src/Galley/Types/UserList.hs b/services/galley/src/Galley/Types/UserList.hs index 071403b5c9d..da00086d6a7 100644 --- a/services/galley/src/Galley/Types/UserList.hs +++ b/services/galley/src/Galley/Types/UserList.hs @@ -34,7 +34,7 @@ data UserList a = UserList { ulLocals :: [a], ulRemotes :: [Remote a] } - deriving (Functor, Foldable, Traversable) + deriving (Show, Functor, Foldable, Traversable) instance Semigroup (UserList a) where UserList locals1 remotes1 <> UserList locals2 remotes2 = From d2751e0150ff4a9fcff0a69692bcc4220bd0852b Mon Sep 17 00:00:00 2001 From: Paolo Capriotti Date: Fri, 25 Oct 2024 13:59:35 +0200 Subject: [PATCH 4/7] Add federationProtocol option to galley chart --- charts/galley/templates/configmap.yaml | 3 +++ charts/galley/values.yaml | 4 ++++ services/galley/galley.integration.yaml | 1 + 3 files changed, 8 insertions(+) diff --git a/charts/galley/templates/configmap.yaml b/charts/galley/templates/configmap.yaml index cf1426e8adb..7b790a8c596 100644 --- a/charts/galley/templates/configmap.yaml +++ b/charts/galley/templates/configmap.yaml @@ -83,6 +83,9 @@ data: {{ fail "settings.conversationCodeURI and settings.multiIngress are mutually exclusive" }} {{- end }} federationDomain: {{ .settings.federationDomain }} + {{- if .settings.federationProtocols }} + federationProtocols: {{ .settings.federationProtocols }} + {{- end }} {{- if $.Values.secrets.mlsPrivateKeys }} mlsPrivateKeyPaths: removal: diff --git a/charts/galley/values.yaml b/charts/galley/values.yaml index 877a2734039..71045b680b9 100644 --- a/charts/galley/values.yaml +++ b/charts/galley/values.yaml @@ -75,6 +75,10 @@ config: iterations: 1 parallelism: 32 memory: 180224 # 176 MiB + + # To disable proteus for new federated conversations: + # federationProtocols: ["mls"] + featureFlags: # see #RefConfigOptions in `/docs/reference` (https://github.com/wireapp/wire-server/) appLock: defaults: diff --git a/services/galley/galley.integration.yaml b/services/galley/galley.integration.yaml index 234c1b76037..c76165b1bd0 100644 --- a/services/galley/galley.integration.yaml +++ b/services/galley/galley.integration.yaml @@ -50,6 +50,7 @@ settings: # Once set, DO NOT change it: if you do, existing users may have a broken experience and/or stop working # Remember to keep it the same in Brig federationDomain: example.com + federationProtocols: ["mls", "proteus"] mlsPrivateKeyPaths: removal: ed25519: test/resources/backendA/ed25519.pem From 6b931b71dda57205c0480da32827ac0bac5188c6 Mon Sep 17 00:00:00 2001 From: Paolo Capriotti Date: Fri, 25 Oct 2024 14:04:21 +0200 Subject: [PATCH 5/7] Add documentation for federationProtocols --- docs/src/developer/reference/config-options.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/docs/src/developer/reference/config-options.md b/docs/src/developer/reference/config-options.md index ae843566522..a22b947c04b 100644 --- a/docs/src/developer/reference/config-options.md +++ b/docs/src/developer/reference/config-options.md @@ -464,6 +464,18 @@ federator: clientPrivateKey: client-key.pem ``` +### Federation protocols + +A backend can restrict creation of new federated conversations according to the protocol used (Proteus or MLS). This is controlled by the `federationProtocols` setting. For example: + +```yaml +galley: + settings: + federationProtocols: ["mls"] +``` + +will prevent the creation of a Proteus conversation containing federated users, and will prevent federated users from joining a Proteus conversation on this backend. However, existing Proteus conversations with federated users will continue to function, and users of this backend will be allowed to join new and existing Proteus conversations on federated backends. + ### Outlook calendar integration This feature setting only applies to the Outlook Calendar extension for Wire. As it is an external service, it should only be configured through this feature flag and otherwise ignored by the backend. From 5edb48d16b0521fb707224321367334e81bd9f3d Mon Sep 17 00:00:00 2001 From: Paolo Capriotti Date: Fri, 25 Oct 2024 14:20:44 +0200 Subject: [PATCH 6/7] Test effect of federationProtocol on local users Local users can be added to remote proteus conversations even if `federationProtocol` is set to `mls` only on the local backend. --- integration/test/Test/Conversation.hs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/integration/test/Test/Conversation.hs b/integration/test/Test/Conversation.hs index 0140b25afb2..a9ef7595714 100644 --- a/integration/test/Test/Conversation.hs +++ b/integration/test/Test/Conversation.hs @@ -931,3 +931,5 @@ testNoFederationWithProteus = do bindResponse (postConversation alice defProteus {qualifiedUsers = [bob]}) $ \resp -> do resp.status `shouldMatchInt` 409 resp.json %. "label" `shouldMatch` "federation-disabled-for-protocol" + + void $ postConversation bob defProteus {qualifiedUsers = [alice]} >>= getJSON 201 From f4c1f873a9dc1e13453ae633208f72cf7d6e58b4 Mon Sep 17 00:00:00 2001 From: Paolo Capriotti Date: Fri, 25 Oct 2024 16:04:29 +0200 Subject: [PATCH 7/7] Add CHANGELOG entry --- changelog.d/2-features/no-federated-proteus | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/2-features/no-federated-proteus diff --git a/changelog.d/2-features/no-federated-proteus b/changelog.d/2-features/no-federated-proteus new file mode 100644 index 00000000000..cfc0fcd7b7c --- /dev/null +++ b/changelog.d/2-features/no-federated-proteus @@ -0,0 +1 @@ +Add `federationProtocols` setting to galley, which can be used to disable the creation of federated conversations with a given protocol