Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions changelog.d/0-release-notes/WPB-227
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
There is a new optional Boolean option, `multiSFT.enabled`, in `brig.yaml`,
allowing calls between federated SFT servers. If provided, the field
`is_federating` in the response of `/calls/config/v2` will reflect
`multiSFT.enabled`'s value.

Example:

```
# [brig.yaml]
multiSFT:
enabled: true
```

Also, the optional object `sftToken` with its fields `ttl` and `secret` define
whether an SFT credential would be rendered in the response of
`/calls/config/v2`. The field `ttl` determines the seconds for the credential to
be valid and `secret` is the path to the secret shared with SFT to create
credentials.

Example:

```
# [brig.yaml]
sft:
sftBaseDomain: sft.wire.example.com
sftSRVServiceName: sft
sftDiscoveryIntervalSeconds: 10
sftListLength: 20
sftToken:
ttl: 120
secret: /path/to/secret
```
8 changes: 8 additions & 0 deletions charts/brig/templates/configmap.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ data:
host: gundeck
port: 8080

multiSFT: {{ .multiSFT.enabled }}
{{- if .enableFederation }}
# TODO remove this
federator:
Expand Down Expand Up @@ -209,6 +210,13 @@ data:
{{- if .sftDiscoveryIntervalSeconds }}
sftDiscoveryIntervalSeconds: {{ .sftDiscoveryIntervalSeconds }}
{{- end }}
{{- if .sftToken }}
sftToken:
{{- with .sftToken }}
ttl: {{ .ttl }}
secret: {{ .secret }}
{{- end }}
{{- end }}
{{- end }}
{{- end }}

Expand Down
2 changes: 2 additions & 0 deletions charts/brig/templates/tests/configmap.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ data:
host: spar
port: 8080

multiSFT: false

# TODO remove this
federator:
host: federator
Expand Down
2 changes: 2 additions & 0 deletions charts/brig/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ config:
# -- If set to false, 'dynamoDBEndpoint' _must_ be set.
randomPrekeys: true
useSES: true
multiSFT:
enabled: false # keep multiSFT default in sync with sft chart's multiSFT.enabled
enableFederation: false # keep enableFederation default in sync with galley and cargohold chart's config.enableFederation as well as wire-server chart's tags.federation
# Not used if enableFederation is false
rabbitmq:
Expand Down
2 changes: 1 addition & 1 deletion charts/sftd/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ turnDiscoveryEnabled: false
# Allow establishing calls involving remote SFT servers (e.g. for Federation)
# Requires appVersion 3.0.9 or later
multiSFT:
enabled: false
enabled: false # keep multiSFT default in sync with brig chart's config.multiSFT
# For sftd versions up to 3.1.3, sftd uses the TURN servers advertised at a
# discovery URL.
turnDiscoveryURL: ""
Expand Down
24 changes: 24 additions & 0 deletions docs/src/developer/reference/config-options.md
Original file line number Diff line number Diff line change
Expand Up @@ -517,6 +517,30 @@ This setting assumes that the sft load balancer has been deployed with the `sftd

Additionally if `setSftListAllServers` is set to `enabled` (disabled by default) then the `/calls/config/v2` endpoint will include a list of all servers that are load balanced by `setSftStaticUrl` at field `sft_servers_all`. This is required to enable calls between federated instances of Wire.

Calls between federated SFT servers can be enabled using the optional boolean `multiSFT.enabled`. If provided, the field `is_federating` in the response of `/calls/config/v2` will reflect `multiSFT.enabled`'s value.

```
# [brig.yaml]
multiSFT:
enabled: true
```

Also, the optional object `sftToken` with its fields `ttl` and `secret` define whether an SFT credential would be rendered in the response of `/calls/config/v2`. The field `ttl` determines the seconds for the credential to be valid and `secret` is the path to the secret shared with SFT to create credentials.

Example:

```
# [brig.yaml]
sft:
sftBaseDomain: sft.wire.example.com
sftSRVServiceName: sft
sftDiscoveryIntervalSeconds: 10
sftListLength: 20
sftToken:
ttl: 120
secret: /path/to/secret
```

### Locale


Expand Down
2 changes: 2 additions & 0 deletions hack/helm_vars/wire-server/values.yaml.gotmpl
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ brig:
accessTokenTimeout: 30
providerTokenTimeout: 60
enableFederation: true # keep in sync with galley.config.enableFederation, cargohold.config.enableFederation and tags.federator!
multiSFT:
enabled: false # keep multiSFT default in sync with brig and sft chart's config.multiSFT
optSettings:
setActivationTimeout: 10
setVerificationTimeout: 10
Expand Down
6 changes: 6 additions & 0 deletions integration/test/API/Brig.hs
Original file line number Diff line number Diff line change
Expand Up @@ -636,3 +636,9 @@ renewToken :: (HasCallStack, MakesValue uid) => uid -> String -> App Response
renewToken caller cookie = do
req <- baseRequest caller Brig Versioned "access"
submit "POST" (addHeader "Cookie" ("zuid=" <> cookie) req)

-- | https://staging-nginz-https.zinfra.io/v5/api/swagger-ui/#/default/get_calls_config_v2
getCallsConfigV2 :: (HasCallStack, MakesValue user) => user -> App Response
getCallsConfigV2 user = do
req <- baseRequest user Brig Versioned $ joinHttpPath ["calls", "config", "v2"]
submit "GET" req
107 changes: 106 additions & 1 deletion integration/test/Test/Brig.hs
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
module Test.Brig where

import API.Brig
import qualified API.BrigInternal as BrigI
import API.Common (randomName)
import API.Common
import Data.Aeson.Types hiding ((.=))
import Data.List.Split
import Data.String.Conversions
import qualified Data.UUID as UUID
import qualified Data.UUID.V4 as UUID
import GHC.Stack
import SetupHelpers
import System.IO.Extra
import Testlib.Assertions
import Testlib.Prelude
import UnliftIO.Temporary

testCrudFederationRemotes :: HasCallStack => App ()
testCrudFederationRemotes = do
Expand Down Expand Up @@ -124,3 +128,104 @@ testCrudFederationRemoteTeams = do
l <- resp.json & asList
remoteTeams <- forM l (\e -> e %. "team_id" & asString)
when (any (\t -> t `notElem` remoteTeams) tids) $ assertFailure "Expected response to contain all of the teams"

testSFTCredentials :: HasCallStack => App ()
testSFTCredentials = do
let ttl = (60 :: Int)
withSystemTempFile "sft-secret" $ \secretFile secretHandle -> do
liftIO $ do
hPutStr secretHandle "xMtZyTpu=Leb?YKCoq#BXQR:gG^UrE83dNWzFJ2VcD"
hClose secretHandle
withModifiedBackend
( def
{ brigCfg =
( setField "sft.sftBaseDomain" "integration-tests.zinfra.io"
. setField "sft.sftToken.ttl" ttl
. setField "sft.sftToken.secret" secretFile
. setField "optSettings.setSftListAllServers" "enabled"
)
}
)
$ \domain -> do
user <- randomUser domain def
bindResponse (getCallsConfigV2 user) \resp -> do
sftServersAll <- resp.json %. "sft_servers_all" & asList
when (null sftServersAll) $ assertFailure "sft_servers_all missing"
for_ sftServersAll $ \s -> do
cred <- s %. "credential" & asString
when (null cred) $ assertFailure "credential missing"
usr <- s %. "username" & asString
let parts = splitOn "." usr
when (length parts /= 5) $ assertFailure "username should have 5 parts"
when (take 2 (head parts) /= "d=") $ assertFailure "missing expiry time identifier"
when (take 2 (parts !! 1) /= "v=") $ assertFailure "missing version identifier"
when (take 2 (parts !! 2) /= "k=") $ assertFailure "missing key ID identifier"
when (take 2 (parts !! 3) /= "s=") $ assertFailure "missing federation identifier"
when (take 2 (parts !! 4) /= "r=") $ assertFailure "missing random data identifier"
for_ parts $ \part -> when (length part < 3) $ assertFailure ("value missing for " <> part)

testSFTNoCredentials :: HasCallStack => App ()
testSFTNoCredentials = withModifiedBackend
( def
{ brigCfg =
( setField "sft.sftBaseDomain" "integration-tests.zinfra.io"
. setField "optSettings.setSftListAllServers" "enabled"
)
}
)
$ \domain -> do
user <- randomUser domain def
bindResponse (getCallsConfigV2 user) \resp -> do
sftServersAll <- resp.json %. "sft_servers_all" & asList
when (null sftServersAll) $ assertFailure "sft_servers_all missing"
for_ sftServersAll $ \s -> do
credM <- lookupField s "credential"
when (isJust credM) $ assertFailure "should not generate credential"
usrM <- lookupField s "username"
when (isJust usrM) $ assertFailure "should not generate username"

testSFTFederation :: HasCallStack => App ()
testSFTFederation = do
withModifiedBackend
( def
{ brigCfg =
( setField "sft.sftBaseDomain" "integration-tests.zinfra.io"
. removeField "multiSFT"
)
}
)
$ \domain -> do
user <- randomUser domain def
bindResponse (getCallsConfigV2 user) \resp -> do
isFederatingM <- lookupField resp.json "is_federating"
when (isJust isFederatingM) $ assertFailure "is_federating should not be present"
withModifiedBackend
( def
{ brigCfg =
( setField "sft.sftBaseDomain" "integration-tests.zinfra.io"
. setField "multiSFT" True
)
}
)
$ \domain -> do
user <- randomUser domain def
bindResponse (getCallsConfigV2 user) \resp -> do
isFederating <-
maybe (assertFailure "is_federating missing") asBool
=<< lookupField resp.json "is_federating"
unless isFederating $ assertFailure "is_federating should be true"
withModifiedBackend
( def
{ brigCfg =
( setField "sft.sftBaseDomain" "integration-tests.zinfra.io"
. setField "multiSFT" False
)
}
)
$ \domain -> do
user <- randomUser domain def
bindResponse (getCallsConfigV2 user) \resp -> do
isFederating <-
maybe (assertFailure "is_federating missing") asBool
=<< lookupField resp.json "is_federating"
when isFederating $ assertFailure "is_federating should be false"
12 changes: 7 additions & 5 deletions integration/test/Testlib/JSON.hs
Original file line number Diff line number Diff line change
Expand Up @@ -236,8 +236,9 @@ lookupField val selector = do
go k [] v = get v k
go k (k2 : ks) v = get v k >>= assertField v k >>= go k2 ks

-- Update nested fields
-- | Update nested fields
-- E.g. ob & "foo.bar.baz" %.= ("quux" :: String)
-- The selector path will be created if non-existing.
setField ::
forall a b.
(HasCallStack, MakesValue a, ToJSON b) =>
Expand All @@ -253,7 +254,8 @@ setField selector v x = do
member :: (HasCallStack, MakesValue a) => String -> a -> App Bool
member k x = KM.member (KM.fromString k) <$> (make x >>= asObject)

-- Update nested fields, using the old value with a stateful action
-- | Update nested fields, using the old value with a stateful action
-- The selector path will be created if non-existing.
modifyField :: (HasCallStack, MakesValue a, ToJSON b) => String -> (Maybe Value -> App b) -> a -> App Value
modifyField selector up x = do
v <- make x
Expand All @@ -268,7 +270,7 @@ modifyField selector up x = do
newValue <- toJSON <$> up (KM.lookup k' ob)
pure $ Object $ KM.insert k' newValue ob
go k (k2 : ks) v = do
val <- v %. k
val <- fromMaybe (Object $ KM.empty) <$> lookupField v k
newValue <- go k2 ks val
ob <- asObject v
pure $ Object $ KM.insert (KM.fromString k) newValue ob
Expand Down Expand Up @@ -339,9 +341,9 @@ objQid ob = do
Just v -> pure v
where
select x = runMaybeT $ do
vdom <- MaybeT $ lookupField x "domain"
vdom <- lookupFieldM x "domain"
dom <- MaybeT $ asStringM vdom
vid <- MaybeT $ lookupField x "id"
vid <- lookupFieldM x "id"
id_ <- MaybeT $ asStringM vid
pure (dom, id_)

Expand Down
Loading