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 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/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. diff --git a/integration/test/Test/Conversation.hs b/integration/test/Test/Conversation.hs index e6ae83d1519..a9ef7595714 100644 --- a/integration/test/Test/Conversation.hs +++ b/integration/test/Test/Conversation.hs @@ -908,3 +908,28 @@ 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" + + void $ postConversation bob defProteus {qualifiedUsers = [alice]} >>= getJSON 201 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/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 diff --git a/services/galley/src/Galley/API/Action.hs b/services/galley/src/Galley/API/Action.hs index e96f060855e..e5e7ff6a79d 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 + ) => + ProtocolTag -> + [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, @@ -521,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 4b71e3fcf85..9ab84a07469 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 (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 be813e6ee3d..1f916761387 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, @@ -72,6 +73,7 @@ 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 @@ -136,6 +138,7 @@ data Settings = Settings -- - wire.com -- - example.com _federationDomain :: !Domain, + _federationProtocols :: !(Maybe [ProtocolTag]), _mlsPrivateKeyPaths :: !(Maybe MLSPrivateKeyPaths), -- | FUTUREWORK: 'setFeatureFlags' should be renamed to 'setFeatureConfigs' in all types. _featureFlags :: !FeatureFlags, 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 =