diff --git a/changelog.d/0-release-notes/WPB-4657 b/changelog.d/0-release-notes/WPB-4657 new file mode 100644 index 00000000000..e9953f7934a --- /dev/null +++ b/changelog.d/0-release-notes/WPB-4657 @@ -0,0 +1,4 @@ +The development API is now disabled by default. +The old structure of the server config is compatible with this change. So it is safe to deploy this without changing the server config. +However, if you want to disable the development API explicitly by adding the `development` keyword to the list of disabled APIs, this has to be done during or after the deployment of the services, not before. +For more information see diff --git a/changelog.d/5-internal/WPB-4657 b/changelog.d/5-internal/WPB-4657 new file mode 100644 index 00000000000..caedb616866 --- /dev/null +++ b/changelog.d/5-internal/WPB-4657 @@ -0,0 +1 @@ +The development API version is now disabled by default diff --git a/charts/brig/values.yaml b/charts/brig/values.yaml index ba1411582e4..88b6387b099 100644 --- a/charts/brig/values.yaml +++ b/charts/brig/values.yaml @@ -107,6 +107,7 @@ config: setOAuthMaxActiveRefreshTokens: 10 # 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. + # The default value if not set is `["development"]` which disables the development API versions. # setDisabledAPIVersions: [ v3 ] setFederationStrategy: allowNone setFederationDomainConfigsUpdateFreq: 10 diff --git a/charts/cannon/values.yaml b/charts/cannon/values.yaml index ef45004fca7..3326ec4bac6 100644 --- a/charts/cannon/values.yaml +++ b/charts/cannon/values.yaml @@ -24,6 +24,7 @@ config: # 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. + # The default value if not set is `["development"]` which disables the development API versions. # disabledAPIVersions: [ v3 ] metrics: diff --git a/charts/cargohold/values.yaml b/charts/cargohold/values.yaml index 372c439834b..c129a7ed2c5 100644 --- a/charts/cargohold/values.yaml +++ b/charts/cargohold/values.yaml @@ -32,6 +32,7 @@ config: downloadLinkTTL: 300 # Seconds # 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. + # The default value if not set is `["development"]` which disables the development API versions. # disabledAPIVersions: [ v3 ] serviceAccount: diff --git a/charts/galley/values.yaml b/charts/galley/values.yaml index 9cfcc287d10..54fed71606c 100644 --- a/charts/galley/values.yaml +++ b/charts/galley/values.yaml @@ -57,6 +57,7 @@ config: 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. + # The default value if not set is `["development"]` which disables the development API versions. # disabledAPIVersions: [ v3 ] # The lifetime of a conversation guest link in seconds. Must be a value 0 < x <= 31536000 (365 days) # Default is 31536000 (365 days) if not set diff --git a/charts/gundeck/values.yaml b/charts/gundeck/values.yaml index 87c338a69d6..db048ce314f 100644 --- a/charts/gundeck/values.yaml +++ b/charts/gundeck/values.yaml @@ -44,6 +44,7 @@ config: soft: 1000 # 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. + # The default value if not set is `["development"]` which disables the development API versions. # disabledAPIVersions: [ v3 ] # Maximum number of bytes loaded into memory when fetching (referenced) payloads. diff --git a/charts/proxy/values.yaml b/charts/proxy/values.yaml index 2b9c5fc60ce..25f2dd57543 100644 --- a/charts/proxy/values.yaml +++ b/charts/proxy/values.yaml @@ -21,6 +21,7 @@ config: proxy: {} # 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. + # The default value if not set is `["development"]` which disables the development API versions. # disabledAPIVersions: [ v3 ] podSecurityContext: diff --git a/charts/spar/values.yaml b/charts/spar/values.yaml index 9cb6c2c969d..80f795fb467 100644 --- a/charts/spar/values.yaml +++ b/charts/spar/values.yaml @@ -34,6 +34,7 @@ config: proxy: {} # 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. + # The default value if not set is `["development"]` which disables the development API versions. # disabledAPIVersions: [ v3 ] podSecurityContext: diff --git a/docs/src/developer/reference/config-options.md b/docs/src/developer/reference/config-options.md index 6e5fd477d8d..7bdc1b38609 100644 --- a/docs/src/developer/reference/config-options.md +++ b/docs/src/developer/reference/config-options.md @@ -591,10 +591,6 @@ See {ref}`configure-federation-strategy-in-brig` (since [PR#3260](https://github ### API Versioning -#### `setEnableDevelopmentVersions` - -This options determines whether development versions should be enabled. If set to `False`, all development versions are removed from the `supported` field of the `/api-version` endpoint. Note that they are still listed in the `development` field, and continue to work normally. - ### OAuth For more information on OAuth please refer to . @@ -654,10 +650,9 @@ It is possible to disable one ore more API versions. When an API version is disa Each of the services brig, cannon, cargohold, galley, gundeck, proxy, spar should to be configured with the same set of disable API versions in each service's values.yaml config files. - For example to disable API version v3, you need to configure: -``` +```yaml # brig's values.yaml config.optSettings.setDisabledAPIVersions: [ v3 ] @@ -680,7 +675,13 @@ config.disabledAPIVersions: [ v3 ] config.disabledAPIVersions: [ v3 ] ``` -The default setting is that no API version is disabled. +The development API version(s) can be disabled either explicitly or by adding the `development` keyword to the list of disabled API versions. E.g.: + +```yaml +config.disabledAPIVersions: [ v3, development ] +``` + +The default setting (in case the value is not present in the server configuration) is that all development versions are disabled while all other supported versions are enabled. To enable all versions including the development version set the value to be empty: `[]`. ## Settings in cargohold diff --git a/hack/helm_vars/wire-server/values.yaml.gotmpl b/hack/helm_vars/wire-server/values.yaml.gotmpl index 538a9df9859..4d4838df465 100644 --- a/hack/helm_vars/wire-server/values.yaml.gotmpl +++ b/hack/helm_vars/wire-server/values.yaml.gotmpl @@ -95,6 +95,7 @@ brig: setFederationDomain: integration.example.com setFederationStrategy: allowAll setFederationDomainConfigsUpdateFreq: 10 + setDisabledAPIVersions: [] set2FACodeGenerationDelaySecs: 5 setNonceTtlSecs: 300 setDpopMaxSkewSecs: 1 @@ -169,6 +170,7 @@ cannon: limits: memory: 512Mi drainTimeout: 0 + disabledAPIVersions: [] cargohold: replicaCount: 1 imagePullPolicy: {{ .Values.imagePullPolicy }} @@ -184,6 +186,7 @@ cargohold: settings: # See helmfile for the real value federationDomain: integration.example.com + disabledAPIVersions: [] secrets: awsKeyId: dummykey awsSecretKey: dummysecret @@ -218,6 +221,8 @@ galley: conversationCodeURI: https://kube-staging-nginz-https.zinfra.io/conversation-join/ # See helmfile for the real value federationDomain: integration.example.com + disabledAPIVersions: [] + featureFlags: sso: disabled-by-default # this needs to be the default; tests can enable it when needed. legalhold: whitelist-teams-and-implicit-consent @@ -293,6 +298,7 @@ gundeck: setMaxConcurrentNativePushes: hard: 30 soft: 10 + disabledAPIVersions: [] secrets: awsKeyId: dummykey awsSecretKey: dummysecret @@ -341,6 +347,8 @@ proxy: giphy = "..." spotify = "Basic ..." } + config: + disabledAPIVersions: [] spar: replicaCount: 1 imagePullPolicy: {{ .Values.imagePullPolicy }} @@ -368,6 +376,7 @@ spar: - type: ContactSupport company: Example Company email: email:backend+spar@wire.com + disabledAPIVersions: [] tests: {{- if .Values.uploadXml }} config: diff --git a/integration/integration.cabal b/integration/integration.cabal index 1335572e592..cc5338954ce 100644 --- a/integration/integration.cabal +++ b/integration/integration.cabal @@ -137,6 +137,7 @@ library Test.Services Test.Swagger Test.User + Test.Version Testlib.App Testlib.Assertions Testlib.Cannon diff --git a/integration/test/API/Brig.hs b/integration/test/API/Brig.hs index 514f5c8ec53..2df25d354e7 100644 --- a/integration/test/API/Brig.hs +++ b/integration/test/API/Brig.hs @@ -398,11 +398,17 @@ replaceKeyPackages cid suites kps = do & addQueryParams [("ciphersuites", intercalate "," (map (.code) suites))] & addJSONObject ["key_packages" .= map (T.decodeUtf8 . Base64.encode) kps] -getSelf :: HasCallStack => String -> String -> App Response -getSelf domain uid = do - let user = object ["domain" .= domain, "id" .= uid] - req <- baseRequest user Brig Versioned "/self" - submit "GET" req +-- | https://staging-nginz-https.zinfra.io/v6/api/swagger-ui/#/default/get_self +getSelf :: (HasCallStack, MakesValue user) => user -> App Response +getSelf = getSelfWithVersion Versioned + +getSelfWithVersion :: (HasCallStack, MakesValue user) => Versioned -> user -> App Response +getSelfWithVersion v user = baseRequest user Brig v "/self" >>= submit "GET" + +-- | https://staging-nginz-https.zinfra.io/v6/api/swagger-ui/#/default/get_self +-- this is a low-level version of `getSelf` for testing some error conditions. +getSelf' :: HasCallStack => String -> String -> App Response +getSelf' domain uid = getSelfWithVersion Versioned $ object ["domain" .= domain, "id" .= uid] getUserSupportedProtocols :: (HasCallStack, MakesValue user, MakesValue target) => diff --git a/integration/test/Test/Demo.hs b/integration/test/Test/Demo.hs index 9691212c5f9..b8a7e6c8155 100644 --- a/integration/test/Test/Demo.hs +++ b/integration/test/Test/Demo.hs @@ -105,7 +105,7 @@ testDynamicBackend = do ownDomain <- objDomain OwnDomain user <- randomUser OwnDomain def uid <- objId user - bindResponse (BrigP.getSelf ownDomain uid) $ \resp -> do + bindResponse (BrigP.getSelf user) $ \resp -> do resp.status `shouldMatchInt` 200 (resp.json %. "id") `shouldMatch` objId user @@ -117,18 +117,18 @@ testDynamicBackend = do resp.json %. "setRestrictUserCreation" `shouldMatch` False -- user created in own domain should not be found in dynamic backend - bindResponse (BrigP.getSelf dynDomain uid) $ \resp -> do + bindResponse (BrigP.getSelf' dynDomain uid) $ \resp -> do resp.status `shouldMatchInt` 404 -- now create a user in the dynamic backend userD1 <- randomUser dynDomain def uidD1 <- objId userD1 - bindResponse (BrigP.getSelf dynDomain uidD1) $ \resp -> do + bindResponse (BrigP.getSelf userD1) $ \resp -> do resp.status `shouldMatchInt` 200 (resp.json %. "id") `shouldMatch` objId userD1 -- the d1 user should not be found in the own domain - bindResponse (BrigP.getSelf ownDomain uidD1) $ \resp -> do + bindResponse (BrigP.getSelf' ownDomain uidD1) $ \resp -> do resp.status `shouldMatchInt` 404 testStartMultipleDynamicBackends :: HasCallStack => App () diff --git a/integration/test/Test/Swagger.hs b/integration/test/Test/Swagger.hs index d3e89f21d7b..76cf6ddc381 100644 --- a/integration/test/Test/Swagger.hs +++ b/integration/test/Test/Swagger.hs @@ -23,13 +23,6 @@ testSwagger = do dev <- resp.json %. "development" & asSetOf asIntegral pure $ sup <> dev assertBool ("unexpected actually existing versions: " <> show actualVersions) $ - -- make sure nobody has added a new version without adding it to `existingVersions`. - -- make sure nobody has added a new version without adding it to `existingVersions`. - -- ("subset" because blocked versions like v3 are not actually existing, but still - -- ("subset" because blocked versions like v3 are not actually existing, but still - -- documented.) - -- documented.) - -- make sure nobody has added a new version without adding it to `existingVersions`. -- ("subset" because blocked versions like v3 are not actually existing, but still -- documented.) diff --git a/integration/test/Test/Version.hs b/integration/test/Test/Version.hs new file mode 100644 index 00000000000..92e768b20ac --- /dev/null +++ b/integration/test/Test/Version.hs @@ -0,0 +1,109 @@ +module Test.Version where + +import API.Brig +import qualified Data.Set as Set +import SetupHelpers +import Testlib.Prelude + +newtype Versioned' = Versioned' Versioned + +-- | This instance is used to generate tests for some of the versions. (Not checking all of them for time efficiency reasons) +instance HasTests x => HasTests (Versioned' -> x) where + mkTests m n s f x = + mkTests m (n <> "[version=unversioned]") s f (x (Versioned' Unversioned)) + <> mkTests m (n <> "[version=versioned]") s f (x (Versioned' Versioned)) + <> mkTests m (n <> "[version=v1]") s f (x (Versioned' (ExplicitVersion 1))) + <> mkTests m (n <> "[version=v3]") s f (x (Versioned' (ExplicitVersion 3))) + <> mkTests m (n <> "[version=v6]") s f (x (Versioned' (ExplicitVersion 6))) + +testVersion :: Versioned' -> App () +testVersion (Versioned' v) = withModifiedBackend + def {brigCfg = setField "optSettings.setDisabledAPIVersions" ([] :: [String])} + $ \dom -> + bindResponse (baseRequest dom Brig v "/api-version" >>= submit "GET") $ \resp -> do + resp.status `shouldMatchInt` 200 + dev <- resp.json %. "development" & asSet + supported <- resp.json %. "supported" & asSet + domain <- resp.json %. "domain" & asString + federation <- resp.json %. "federation" & asBool + + -- currently there is only one development version + -- it is however theoretically possible to have multiple development versions + length dev `shouldMatchInt` 1 + domain `shouldMatch` dom + federation `shouldMatch` True + + unless (null (Set.intersection supported dev)) $ + assertFailure "development and supported versions should not overlap" + +testVersionUnsupported :: App () +testVersionUnsupported = bindResponse (baseRequest OwnDomain Brig (ExplicitVersion 500) "/api-version" >>= submit "GET") $ + \resp -> do + resp.status `shouldMatchInt` 404 + resp.json %. "label" `shouldMatch` "unsupported-version" + +testVersionDisabled :: App () +testVersionDisabled = withModifiedBackend + def {brigCfg = setField "optSettings.setDisabledAPIVersions" ["v2"]} + $ \domain -> do + do + user <- randomUser OwnDomain def + void $ getSelfWithVersion (ExplicitVersion 2) user >>= getJSON 200 + + do + user <- randomUser domain def + bindResponse (getSelfWithVersion (ExplicitVersion 2) user) $ \resp -> do + resp.status `shouldMatchInt` 404 + resp.json %. "label" `shouldMatch` "unsupported-version" + + void $ getSelfWithVersion (ExplicitVersion 1) user >>= getJSON 200 + void $ getSelfWithVersion (ExplicitVersion 3) user >>= getJSON 200 + void $ getSelfWithVersion (ExplicitVersion 4) user >>= getJSON 200 + void $ getSelfWithVersion (ExplicitVersion 5) user >>= getJSON 200 + void $ getSelfWithVersion (ExplicitVersion 6) user >>= getJSON 200 + void $ getSelfWithVersion Unversioned user >>= getJSON 200 + +testVersionDisabledNotAdvertised :: App () +testVersionDisabledNotAdvertised = do + allVersions <- bindResponse (baseRequest OwnDomain Brig Versioned "/api-version" >>= submit "GET") $ \resp -> + (<>) + <$> (resp.json %. "development" & asList >>= traverse asInt) + <*> (resp.json %. "supported" & asList >>= traverse asInt) + forM_ allVersions testWithVersion + where + testWithVersion :: Int -> App () + testWithVersion v = withModifiedBackend + def {brigCfg = setField "optSettings.setDisabledAPIVersions" ["v" <> show v]} + $ \domain -> do + bindResponse (getAPIVersion domain) $ \resp -> do + resp.status `shouldMatchInt` 200 + dev <- resp.json %. "development" & asList >>= traverse asInt + supported <- resp.json %. "supported" & asList >>= traverse asInt + + assertBool "supported versions should not be empty" $ not (null supported) + assertBool "the disabled version should not be propagated as dev version" $ v `notElem` dev + assertBool "the disabled version should not be propagated as supported version" $ v `notElem` supported + +testVersionDisabledDevNotAdvertised :: App () +testVersionDisabledDevNotAdvertised = withModifiedBackend + def {brigCfg = setField "optSettings.setDisabledAPIVersions" ["development"]} + $ \domain -> do + bindResponse (getAPIVersion domain) $ \resp -> do + resp.status `shouldMatchInt` 200 + dev <- resp.json %. "development" & asList + supported <- resp.json %. "supported" & asList + + assertBool "supported versions should not be empty" $ not (null supported) + assertBool "development versions should be empty" $ null dev + +testVersionDevDisabledPerDefault :: App () +testVersionDevDisabledPerDefault = withModifiedBackend + def {brigCfg = removeField "optSettings.setDisabledAPIVersions"} + $ \domain -> do + bindResponse (getAPIVersion domain) $ \resp -> do + resp.status `shouldMatchInt` 200 + dev <- resp.json %. "development" & asList + supported <- resp.json %. "supported" & asList + + assertBool "supported versions should not be empty" $ not (null supported) + assertBool "development versions should be empty" $ null dev diff --git a/integration/test/Testlib/JSON.hs b/integration/test/Testlib/JSON.hs index cffcd62201e..59aa2400ff4 100644 --- a/integration/test/Testlib/JSON.hs +++ b/integration/test/Testlib/JSON.hs @@ -131,6 +131,9 @@ asListOf :: HasCallStack => (Value -> App b) -> MakesValue a => a -> App [b] asListOf makeElem x = asList x >>= mapM makeElem +asSet :: HasCallStack => MakesValue a => a -> App (Set.Set Value) +asSet = fmap Set.fromList . asList + asSetOf :: (HasCallStack, Ord b) => (Value -> App b) -> MakesValue a => a -> App (Set.Set b) asSetOf makeElem x = Set.fromList <$> asListOf makeElem x diff --git a/libs/wire-api/src/Wire/API/Routes/Version.hs b/libs/wire-api/src/Wire/API/Routes/Version.hs index 67136f1f054..d49c8129558 100644 --- a/libs/wire-api/src/Wire/API/Routes/Version.hs +++ b/libs/wire-api/src/Wire/API/Routes/Version.hs @@ -1,3 +1,4 @@ +{-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE StandaloneKindSignatures #-} {-# LANGUAGE TemplateHaskell #-} @@ -31,9 +32,11 @@ module Wire.API.Routes.Version Version (..), versionInt, VersionNumber (..), + VersionExp (..), supportedVersions, isDevelopmentVersion, developmentVersions, + expandVersionExp, -- * Servant combinators Until, @@ -41,11 +44,12 @@ module Wire.API.Routes.Version -- * Swagger instances SpecialiseToVersion, + VersionExpSetDefaultDev (..), ) where import Control.Error (note) -import Control.Lens ((?~)) +import Control.Lens (makePrisms, (?~)) import Data.Aeson (FromJSON, ToJSON (..)) import Data.Aeson qualified as Aeson import Data.Bifunctor @@ -55,6 +59,8 @@ import Data.ByteString.Lazy qualified as LBS import Data.Domain import Data.OpenApi qualified as S import Data.Schema +import Data.Set qualified as Data +import Data.Set qualified as Set import Data.Singletons.Base.TH import Data.Text qualified as Text import Data.Text.Encoding as Text @@ -64,7 +70,7 @@ import Servant import Servant.API.Extended.RawM import Wire.API.Deprecated import Wire.API.Routes.MultiVerb -import Wire.API.Routes.Named +import Wire.API.Routes.Named hiding (unnamed) import Wire.API.VersionInfo import Wire.Arbitrary (Arbitrary, GenericUniform (GenericUniform)) @@ -204,6 +210,46 @@ isDevelopmentVersion _ = True developmentVersions :: [Version] developmentVersions = filter isDevelopmentVersion supportedVersions +-- Version keywords + +-- | A version "expression" which can be used when disabling versions in a +-- configuration file. +data VersionExp + = -- | A fixed version. + VersionExpConst Version + | -- | All development versions. + VersionExpDevelopment + deriving (Show, Eq, Ord, Generic) + +$(makePrisms ''VersionExp) + +instance ToSchema VersionExp where + schema = + named "VersionExp" $ + tag _VersionExpConst (unnamed schema) + <> tag + _VersionExpDevelopment + ( unnamed + ( enum @Text "VersionExpDevelopment" (element "development" ()) + ) + ) + +deriving via Schema VersionExp instance (FromJSON VersionExp) + +deriving via Schema VersionExp instance (ToJSON VersionExp) + +-- | Expand a version expression into a set of versions. +expandVersionExp :: VersionExp -> Set Version +expandVersionExp (VersionExpConst v) = Set.singleton v +expandVersionExp VersionExpDevelopment = Set.fromList developmentVersions + +newtype VersionExpSetDefaultDev = VersionExpSetDefaultDev {unVersionExpSetDefaultDev :: Data.Set VersionExp} + deriving stock (Eq, Ord, Show, Generic) + deriving newtype (FromJSON, ToJSON, Semigroup) + +instance Monoid VersionExpSetDefaultDev where + mempty = VersionExpSetDefaultDev $ Set.singleton VersionExpDevelopment + -- Version-aware swagger generation $(promoteOrdInstances [''Version]) diff --git a/services/brig/brig.cabal b/services/brig/brig.cabal index 7babc5d1c0a..2af53458908 100644 --- a/services/brig/brig.cabal +++ b/services/brig/brig.cabal @@ -426,7 +426,6 @@ executable brig-integration API.User.RichInfo API.User.Util API.UserPendingActivation - API.Version Federation.End2end Federation.Util Index.Create diff --git a/services/brig/brig.integration.yaml b/services/brig/brig.integration.yaml index 6114e56fa76..71e83cf664c 100644 --- a/services/brig/brig.integration.yaml +++ b/services/brig/brig.integration.yaml @@ -194,6 +194,9 @@ optSettings: setFederationDomainConfigs: - domain: example.com search_policy: full_search + # We explicitly do not disable any API version. Please make sure the configuration value is the same in all these configs: + # brig, cannon, cargohold, galley, gundeck, proxy, spar. + setDisabledAPIVersions: [] set2FACodeGenerationDelaySecs: 5 setNonceTtlSecs: 5 setDpopMaxSkewSecs: 1 diff --git a/services/brig/src/Brig/App.hs b/services/brig/src/Brig/App.hs index 439aae35de3..464641e4bc8 100644 --- a/services/brig/src/Brig/App.hs +++ b/services/brig/src/Brig/App.hs @@ -63,6 +63,7 @@ module Brig.App keyPackageLocalLock, rabbitmqChannel, fsWatcher, + disabledVersions, -- * App Monad AppT (..), @@ -143,6 +144,7 @@ import System.Logger.Class hiding (Settings, settings) import System.Logger.Class qualified as LC import System.Logger.Extended qualified as Log import Util.Options +import Wire.API.Routes.Version import Wire.API.User.Identity (Email) import Wire.API.User.Profile (Locale) @@ -186,7 +188,8 @@ data Env = Env _indexEnv :: IndexEnv, _randomPrekeyLocalLock :: Maybe (MVar ()), _keyPackageLocalLock :: MVar (), - _rabbitmqChannel :: Maybe (MVar Q.Channel) + _rabbitmqChannel :: Maybe (MVar Q.Channel), + _disabledVersions :: Set Version } makeLenses ''Env @@ -246,6 +249,9 @@ newEnv o = do pure Nothing kpLock <- newMVar () rabbitChan <- traverse (Q.mkRabbitMqChannelMVar lgr) o.rabbitmq + let disabledVersionExps = unVersionExpSetDefaultDev $ fold (Opt.setDisabledAPIVersions sett) + allDisabledVersions = foldMap expandVersionExp disabledVersionExps + pure $! Env { _cargohold = mkEndpoint $ Opt.cargohold o, @@ -281,7 +287,8 @@ newEnv o = do _indexEnv = mkIndexEnv o lgr mgr mtr (Opt.galley o), _randomPrekeyLocalLock = prekeyLocalLock, _keyPackageLocalLock = kpLock, - _rabbitmqChannel = rabbitChan + _rabbitmqChannel = rabbitChan, + _disabledVersions = allDisabledVersions } where emailConn _ (Opt.EmailAWS aws) = pure (Just aws, Nothing) diff --git a/services/brig/src/Brig/Options.hs b/services/brig/src/Brig/Options.hs index df9e086d017..fa32c450227 100644 --- a/services/brig/src/Brig/Options.hs +++ b/services/brig/src/Brig/Options.hs @@ -590,10 +590,8 @@ data Settings = Settings setSftListAllServers :: Maybe ListAllSFTServers, setEnableMLS :: Maybe Bool, setKeyPackageMaximumLifetime :: Maybe NominalDiffTime, - -- | When set, development API versions are advertised to clients as supported. - setEnableDevelopmentVersions :: Maybe Bool, -- | Disabled versions are not advertised and are completely disabled. - setDisabledAPIVersions :: Maybe (Set Version), + setDisabledAPIVersions :: Maybe VersionExpSetDefaultDev, -- | Minimum delay in seconds between consecutive attempts to generate a new verification code. -- use `set2FACodeGenerationDelaySecs` as the getter function which always provides a default value set2FACodeGenerationDelaySecsInternal :: !(Maybe Int), @@ -930,7 +928,6 @@ Lens.makeLensesFor ("setSftListAllServers", "sftListAllServers"), ("setFederationDomainConfigs", "federationDomainConfigs"), ("setFederationStrategy", "federationStrategy"), - ("setEnableDevelopmentVersions", "enableDevelopmentVersions"), ("setRestrictUserCreation", "restrictUserCreation"), ("setEnableMLS", "enableMLS"), ("setOAuthEnabledInternal", "oauthEnabledInternal"), diff --git a/services/brig/src/Brig/Run.hs b/services/brig/src/Brig/Run.hs index 4a5ed6239a6..1920db1a85d 100644 --- a/services/brig/src/Brig/Run.hs +++ b/services/brig/src/Brig/Run.hs @@ -126,7 +126,7 @@ mkApp o = do middleware :: Env -> (RequestId -> Wai.Application) -> Wai.Application middleware e = -- this rewrites the request, so it must be at the top (i.e. applied last) - versionMiddleware (fold (setDisabledAPIVersions (optSettings o))) + versionMiddleware (e ^. disabledVersions) . Metrics.servantPlusWAIPrometheusMiddleware (sitemap @BrigCanonicalEffects) (Proxy @ServantCombinedAPI) . GZip.gunzip . GZip.gzip GZip.def diff --git a/services/brig/src/Brig/Version.hs b/services/brig/src/Brig/Version.hs index 6531283161c..9d16efedd7d 100644 --- a/services/brig/src/Brig/Version.hs +++ b/services/brig/src/Brig/Version.hs @@ -19,7 +19,6 @@ module Brig.Version where import Brig.API.Handler import Brig.App -import Brig.Options import Control.Lens import Data.Set qualified as Set import Imports @@ -31,13 +30,10 @@ versionAPI :: ServerT VersionAPI (Handler r) versionAPI = Named $ do fed <- view federator dom <- viewFederationDomain - dev <- view (settings . enableDevelopmentVersions . to (fromMaybe False)) - disabledVersions <- view (settings . disabledAPIVersions . traverse) - let allVersions = Set.difference (Set.fromList supportedVersions) disabledVersions - devVersions = Set.difference (Set.fromList developmentVersions) disabledVersions - supported - | dev = allVersions - | otherwise = Set.difference allVersions devVersions + disabled <- view disabledVersions + let allVersions = Set.difference (Set.fromList supportedVersions) disabled + devVersions = Set.difference (Set.fromList developmentVersions) disabled + supported = Set.difference allVersions devVersions pure $ VersionInfo { vinfoSupported = VersionNumber <$> toList supported, diff --git a/services/brig/test/integration/API/Version.hs b/services/brig/test/integration/API/Version.hs deleted file mode 100644 index 5ad8e5aaf21..00000000000 --- a/services/brig/test/integration/API/Version.hs +++ /dev/null @@ -1,160 +0,0 @@ --- This file is part of the Wire Server implementation. --- --- Copyright (C) 2022 Wire Swiss GmbH --- --- This program is free software: you can redistribute it and/or modify it under --- the terms of the GNU Affero General Public License as published by the Free --- Software Foundation, either version 3 of the License, or (at your option) any --- later version. --- --- This program is distributed in the hope that it will be useful, but WITHOUT --- ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS --- FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more --- details. --- --- You should have received a copy of the GNU Affero General Public License along --- with this program. If not, see . - -module API.Version (tests) where - -import Bilge -import Bilge.Assert -import Brig.Options -import Brig.Options qualified as Opt -import Control.Lens ((?~)) -import Control.Monad.Catch (MonadCatch) -import Data.Set qualified as Set -import Imports -import Network.Wai.Utilities.Error qualified as Wai -import Test.Tasty -import Test.Tasty.HUnit -import Util -import Wire.API.Routes.Version -import Wire.API.User - -tests :: Manager -> Opts -> Brig -> TestTree -tests p opts brig = - testGroup - "version" - [ test p "GET /api-version" $ testVersion brig, - test p "GET /v1/api-version" $ testVersionV1 brig, - test p "GET /api-version (with dev)" $ testDevVersion opts brig, - test p "GET /v500/api-version" $ testUnsupportedVersion brig, - test p "GET /api-version (federation info)" $ testFederationDomain opts brig, - test p "Disabled version is unsupported" $ testDisabledVersionIsUnsupported opts brig, - test p "Disabled version is not advertised" $ testVersionDisabledSupportedVersion opts brig, - test p "Disabled dev version is not advertised" $ testVersionDisabledDevelopmentVersion opts brig - ] - -testVersion :: Brig -> Http () -testVersion brig = do - vinfo <- - responseJsonError - =<< get (brig . path "/api-version") - vinfoSupported vinfo) @?= supportedVersions \\ developmentVersions - -testVersionV1 :: Brig -> Http () -testVersionV1 brig = do - vinfo <- - responseJsonError - =<< get (apiVersion "v1" . brig . path "api-version") - vinfoSupported vinfo) @?= supportedVersions \\ developmentVersions - -testDevVersion :: Opts -> Brig -> Http () -testDevVersion opts brig = withSettingsOverrides - (opts & optionSettings . enableDevelopmentVersions ?~ True) - $ do - vinfo <- - responseJsonError - =<< get (brig . path "/api-version") - vinfoSupported vinfo) @?= supportedVersions - -testUnsupportedVersion :: Brig -> Http () -testUnsupportedVersion brig = do - e <- - responseJsonError - =<< get (apiVersion "v500" . brig . path "api-version") - Brig -> Http () -testFederationDomain opts brig = do - let domain = setFederationDomain (optSettings opts) - vinfo <- - responseJsonError - =<< get (brig . path "/api-version") - Brig -> Http () -testDisabledVersionIsUnsupported opts brig = do - uid <- userId <$> randomUser brig - - get (apiVersion "v2" . brig . path "/self" . zUser uid) - !!! const 200 === statusCode - - withSettingsOverrides - ( opts - & Opt.optionSettings - . Opt.disabledAPIVersions - ?~ Set.fromList [V2] - ) - $ do - err <- - responseJsonError - =<< get (apiVersion "v2" . brig . path "/self" . zUser uid) - Brig -> Http () -testVersionDisabledSupportedVersion opts brig = do - vinfo <- getVersionInfo brig - liftIO $ filter (== VersionNumber V2) (vinfoSupported vinfo) @?= [VersionNumber V2] - disabledVersionIsNotAdvertised opts brig V2 - -testVersionDisabledDevelopmentVersion :: Opts -> Brig -> Http () -testVersionDisabledDevelopmentVersion opts brig = do - vinfo <- getVersionInfo brig - for_ (listToMaybe (vinfoDevelopment vinfo)) $ \devVersion -> do - liftIO $ filter (== devVersion) (vinfoDevelopment vinfo) @?= [devVersion] - disabledVersionIsNotAdvertised opts brig (fromVersionNumber devVersion) - -disabledVersionIsNotAdvertised :: Opts -> Brig -> Version -> Http () -disabledVersionIsNotAdvertised opts brig version = - withSettingsOverrides - ( opts - & Opt.optionSettings - . Opt.disabledAPIVersions - ?~ Set.fromList [version] - ) - $ do - vinfo <- getVersionInfo brig - liftIO $ filter (== VersionNumber version) (vinfoSupported vinfo) @?= [] - liftIO $ filter (== VersionNumber version) (vinfoDevelopment vinfo) @?= [] - -getVersionInfo :: - (MonadIO m, MonadCatch m, MonadHttp m, HasCallStack) => - Brig -> - m VersionInfo -getVersionInfo brig = - responseJsonError - =<< get (unversioned . brig . path "/api-version") - Internal.API @@ -80,7 +81,7 @@ run o = do let middleware :: Wai.Middleware middleware = - versionMiddleware (fold (o ^. disabledAPIVersions)) + versionMiddleware (foldMap expandVersionExp (unVersionExpSetDefaultDev (fold (o ^. disabledAPIVersions)))) . servantPrometheusMiddleware (Proxy @CombinedAPI) . Gzip.gzip Gzip.def . catchErrors g [Right m] diff --git a/services/cargohold/cargohold.integration.yaml b/services/cargohold/cargohold.integration.yaml index f5736ce01c8..e2432a7f69e 100644 --- a/services/cargohold/cargohold.integration.yaml +++ b/services/cargohold/cargohold.integration.yaml @@ -27,6 +27,9 @@ settings: maxTotalBytes: 27262976 downloadLinkTTL: 300 # Seconds federationDomain: example.com + # We explicitly do not disable any API version. Please make sure the configuration value is the same in all these configs: + # brig, cannon, cargohold, galley, gundeck, proxy, spar. + disabledAPIVersions: [] logLevel: Warn logNetStrings: false diff --git a/services/cargohold/src/CargoHold/Options.hs b/services/cargohold/src/CargoHold/Options.hs index 5152570ce6b..8a06a35a0b5 100644 --- a/services/cargohold/src/CargoHold/Options.hs +++ b/services/cargohold/src/CargoHold/Options.hs @@ -142,7 +142,7 @@ data Settings = Settings -- This is referred to as the 'backend domain' in the public documentation; See -- https://docs.wire.com/how-to/install/configure-federation.html#choose-a-backend-domain-name _federationDomain :: !Domain, - _disabledAPIVersions :: !(Maybe (Set Version)) + _disabledAPIVersions :: !(Maybe VersionExpSetDefaultDev) } deriving (Show, Generic) diff --git a/services/cargohold/src/CargoHold/Run.hs b/services/cargohold/src/CargoHold/Run.hs index 50513f2dcf9..71e81700405 100644 --- a/services/cargohold/src/CargoHold/Run.hs +++ b/services/cargohold/src/CargoHold/Run.hs @@ -56,6 +56,7 @@ import Util.Options import Wire.API.Routes.API import Wire.API.Routes.Internal.Cargohold import Wire.API.Routes.Public.Cargohold +import Wire.API.Routes.Version import Wire.API.Routes.Version.Wai type CombinedAPI = FederationAPI :<|> CargoholdAPI :<|> InternalAPI @@ -81,7 +82,7 @@ mkApp o = Codensity $ \k -> where middleware :: Env -> Wai.Middleware middleware e = - versionMiddleware (fold (o ^. settings . disabledAPIVersions)) + versionMiddleware (foldMap expandVersionExp (unVersionExpSetDefaultDev (fold (o ^. settings . disabledAPIVersions)))) . servantPrometheusMiddleware (Proxy @CombinedAPI) . GZip.gzip GZip.def . catchErrors (e ^. appLogger) [Right $ e ^. metrics] diff --git a/services/galley/galley.integration.yaml b/services/galley/galley.integration.yaml index f73ed5ca066..15146fc480e 100644 --- a/services/galley/galley.integration.yaml +++ b/services/galley/galley.integration.yaml @@ -51,6 +51,9 @@ settings: removal: ed25519: test/resources/ed25519.pem guestLinkTTLSeconds: 604800 + # We explicitly do not disable any API version. Please make sure the configuration value is the same in all these configs: + # brig, cannon, cargohold, galley, gundeck, proxy, spar. + disabledAPIVersions: [] featureFlags: # see #RefConfigOptions in `/docs/reference` sso: disabled-by-default diff --git a/services/galley/src/Galley/Options.hs b/services/galley/src/Galley/Options.hs index 738af7a75f4..7609ad3aac6 100644 --- a/services/galley/src/Galley/Options.hs +++ b/services/galley/src/Galley/Options.hs @@ -145,7 +145,7 @@ data Settings = Settings _mlsPrivateKeyPaths :: !(Maybe MLSPrivateKeyPaths), -- | FUTUREWORK: 'setFeatureFlags' should be renamed to 'setFeatureConfigs' in all types. _featureFlags :: !FeatureFlags, - _disabledAPIVersions :: Maybe (Set Version), + _disabledAPIVersions :: !(Maybe VersionExpSetDefaultDev), -- | The lifetime of a conversation guest link in seconds with the maximum of 1 year (31536000 seconds). -- If not set use the default `defGuestLinkTTLSeconds` _guestLinkTTLSeconds :: !(Maybe GuestLinkTTLSeconds) diff --git a/services/galley/src/Galley/Run.hs b/services/galley/src/Galley/Run.hs index b9643cf7af2..f11a275f333 100644 --- a/services/galley/src/Galley/Run.hs +++ b/services/galley/src/Galley/Run.hs @@ -66,6 +66,7 @@ import System.Logger.Extended (mkLogger) import Util.Options import Wire.API.Routes.API import Wire.API.Routes.Public.Galley +import Wire.API.Routes.Version import Wire.API.Routes.Version.Wai run :: Opts -> IO () @@ -95,7 +96,7 @@ mkApp opts = env <- lift $ App.createEnv metrics opts logger lift $ runClient (env ^. cstate) $ versionCheck schemaVersion let middlewares = - versionMiddleware (opts ^. settings . disabledAPIVersions . traverse) + versionMiddleware (foldMap expandVersionExp (unVersionExpSetDefaultDev (fold (opts ^. settings . disabledAPIVersions)))) . servantPlusWAIPrometheusMiddleware API.waiSitemap (Proxy @CombinedAPI) . GZip.gunzip . GZip.gzip GZip.def diff --git a/services/gundeck/gundeck.integration.yaml b/services/gundeck/gundeck.integration.yaml index 7ceadf3ad87..3a61ada8c84 100644 --- a/services/gundeck/gundeck.integration.yaml +++ b/services/gundeck/gundeck.integration.yaml @@ -41,5 +41,9 @@ settings: hard: 30 # more than this number of threads will not be allowed soft: 10 # more than this number of threads will be warned about +# We explicitly do not disable any API version. Please make sure the configuration value is the same in all these configs: +# brig, cannon, cargohold, galley, gundeck, proxy, spar. +disabledAPIVersions: [] + logLevel: Warn logNetStrings: false diff --git a/services/gundeck/src/Gundeck/Options.hs b/services/gundeck/src/Gundeck/Options.hs index ac484ec8331..401af173e59 100644 --- a/services/gundeck/src/Gundeck/Options.hs +++ b/services/gundeck/src/Gundeck/Options.hs @@ -75,7 +75,7 @@ data Settings = Settings -- However, that parameter is not honoured when using fake-sqs -- (where throttling can thus make sense) _sqsThrottleMillis :: !(Maybe Int), - _disabledAPIVersions :: !(Maybe (Set Version)), + _disabledAPIVersions :: !(Maybe VersionExpSetDefaultDev), -- | Maximum number of bytes loaded into memory when fetching (referenced) payloads. -- Gundeck will return a truncated page if the whole page's payload sizes would exceed this limit in total. -- Inlined payloads can cause greater payload sizes to be loaded into memory regardless of this setting. diff --git a/services/gundeck/src/Gundeck/Run.hs b/services/gundeck/src/Gundeck/Run.hs index 3f7c9d1f6f4..b75fc7101bd 100644 --- a/services/gundeck/src/Gundeck/Run.hs +++ b/services/gundeck/src/Gundeck/Run.hs @@ -53,6 +53,7 @@ import System.Logger qualified as Log import UnliftIO.Async qualified as Async import Util.Options import Wire.API.Routes.Public.Gundeck (GundeckAPI) +import Wire.API.Routes.Version import Wire.API.Routes.Version.Wai run :: Opts -> IO () @@ -81,7 +82,7 @@ run o = do where middleware :: Env -> Wai.Middleware middleware e = - versionMiddleware (fold (o ^. settings . disabledAPIVersions)) + versionMiddleware (foldMap expandVersionExp (foldMap unVersionExpSetDefaultDev (o ^. settings . disabledAPIVersions))) . waiPrometheusMiddleware sitemap . GZip.gunzip . GZip.gzip GZip.def diff --git a/services/proxy/proxy.integration.yaml b/services/proxy/proxy.integration.yaml index 76533910662..486f57f153c 100644 --- a/services/proxy/proxy.integration.yaml +++ b/services/proxy/proxy.integration.yaml @@ -13,6 +13,10 @@ maxConns: 5000 # File containing upstream secrets. secretsConfig: doc/example.config +# We explicitly do not disable any API version. Please make sure the configuration value is the same in all these configs: +# brig, cannon, cargohold, galley, gundeck, proxy, spar. +disabledAPIVersions: [] + # Logging settings logLevel: Info logNetStrings: false diff --git a/services/proxy/src/Proxy/Options.hs b/services/proxy/src/Proxy/Options.hs index 58259956ba6..5ec7ad74a88 100644 --- a/services/proxy/src/Proxy/Options.hs +++ b/services/proxy/src/Proxy/Options.hs @@ -57,7 +57,7 @@ data Opts = Opts _logNetStrings :: !(Maybe (Last Bool)), -- | choose Encoding _logFormat :: !(Maybe (Last LogFormat)), - _disabledAPIVersions :: !(Maybe (Set Version)) + _disabledAPIVersions :: !(Maybe VersionExpSetDefaultDev) } deriving (Show, Generic) diff --git a/services/proxy/src/Proxy/Run.hs b/services/proxy/src/Proxy/Run.hs index 1eb6f1c1e9f..c84c91c0d62 100644 --- a/services/proxy/src/Proxy/Run.hs +++ b/services/proxy/src/Proxy/Run.hs @@ -30,6 +30,7 @@ import Proxy.API (sitemap) import Proxy.Env import Proxy.Options import Proxy.Proxy +import Wire.API.Routes.Version import Wire.API.Routes.Version.Wai run :: Opts -> IO () @@ -40,7 +41,7 @@ run o = do let rtree = compile (sitemap e) let app r k = runProxy e r (route rtree r k) let middleware = - versionMiddleware (fold (o ^. disabledAPIVersions)) + versionMiddleware (foldMap expandVersionExp (foldMap unVersionExpSetDefaultDev (o ^. disabledAPIVersions))) . waiPrometheusMiddleware (sitemap e) . catchErrors (e ^. applog) [Right m] runSettingsWithShutdown s (middleware app) Nothing `finally` destroyEnv e diff --git a/services/spar/spar.integration.yaml b/services/spar/spar.integration.yaml index 6a1eb2f3985..cecf8f3c43c 100644 --- a/services/spar/spar.integration.yaml +++ b/services/spar/spar.integration.yaml @@ -32,6 +32,10 @@ cassandra: # Wire/AWS specific, optional # discoUrl: "https://" +# We explicitly do not disable any API version. Please make sure the configuration value is the same in all these configs: +# brig, cannon, cargohold, galley, gundeck, proxy, spar. +disabledAPIVersions: [] + maxttlAuthreq: 5 # seconds. don't set this too large, it is also the run time of one TTL test. maxttlAuthresp: 7200 # seconds. do not set this to 1h or less, as that is what the mock idp wants. diff --git a/services/spar/src/Spar/Options.hs b/services/spar/src/Spar/Options.hs index 080161c4e7b..31e2e309fcd 100644 --- a/services/spar/src/Spar/Options.hs +++ b/services/spar/src/Spar/Options.hs @@ -67,7 +67,7 @@ data Opts' a = Opts discoUrl :: !(Maybe Text), logNetStrings :: !(Maybe (Last Bool)), logFormat :: !(Maybe (Last LogFormat)), - disabledAPIVersions :: !(Maybe (Set Version)), + disabledAPIVersions :: !(Maybe VersionExpSetDefaultDev), derivedOpts :: !a } deriving (Functor, Show, Generic) diff --git a/services/spar/src/Spar/Run.hs b/services/spar/src/Spar/Run.hs index 2a9a427f64e..5c07c0436de 100644 --- a/services/spar/src/Spar/Run.hs +++ b/services/spar/src/Spar/Run.hs @@ -54,6 +54,7 @@ import System.Logger (Logger, msg, val, (.=), (~~)) import qualified System.Logger as Log import qualified System.Logger.Extended as Log import Util.Options +import Wire.API.Routes.Version (VersionExpSetDefaultDev (unVersionExpSetDefaultDev), expandVersionExp) import Wire.API.Routes.Version.Wai import Wire.Sem.Logger.TinyLog @@ -99,7 +100,7 @@ mkApp sparCtxOpts = do . Bilge.port (sparCtxOpts ^. to galley . port) $ Bilge.empty let wrappedApp = - versionMiddleware (fold (disabledAPIVersions sparCtxOpts)) + versionMiddleware (foldMap expandVersionExp (foldMap unVersionExpSetDefaultDev (disabledAPIVersions sparCtxOpts))) . WU.heavyDebugLogging heavyLogOnly logLevel sparCtxLogger . servantPrometheusMiddleware (Proxy @SparAPI) . WU.catchErrors sparCtxLogger []