Skip to content

Commit 80d06c9

Browse files
committed
Simplify!
[Keep this commit in the history. If we ever need to pull metadata directly from the IdP later, revert it.] - NewIdP goes away entirely, replaced by IdPMetadata. When registering an IdP with an SP, we just send the 'IdPMetadata'. No communication between SP and IdP is expected to take place. - IdPMetadata does not carry a self-sign cert any more. The channel through which IdPMetadata is send to the SP needs to be authenticated elsewhere. - No need for any mock idp service in the integration tests. Removed lots of code that was at least in parts working very nicely! m-(
1 parent 894bf25 commit 80d06c9

File tree

15 files changed

+102
-388
lines changed

15 files changed

+102
-388
lines changed

services/integration.yaml

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -34,23 +34,3 @@ provider:
3434
cert: test/resources/cert.pem
3535
botHost: https://127.0.0.1
3636
botPort: 9000
37-
38-
# Used by spar-integration
39-
# NB: this will run on http, without SSL. this "should" not be the
40-
# case in production, but according to the standard it is technically
41-
# legal.
42-
mockIdp:
43-
metadataURI: https://localhost:9001/meta
44-
issuer: https://localhost:9001/
45-
requestURI: https://localhost:9001/resp
46-
dSigCert: '<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#"><ds:X509Data><ds:X509Certificate>MIIB/zCCASigAwIBAgIOEyBZjWrTHqmgBPAVkAUwDQYJKoZIhvcNAQELBQAwADAeFw0xODA4MTYxMjQzNTdaFw0zODA4MTExMjQzNTdaMAAwgd0wDQYJKoZIhvcNAQEBBQADgcsAMIHHAoHBAMHb4Ne1z2cQD1TXcVmYBy0Q1EnmQl5IncCfC6/eGrp0qpa5sqaQPlRtvS3UEczpAgf9ml+kL6aK56xEBH2Zv/mlkvBEbxASxVha3LhcIg9TNAg0vm2KJBG1pZvHx8OIKhpDCfabkSJF+MxXvtTrp0JTRfQr2BHkegZNX3hCaF5JGyGIMBinTRwEi5duDfNUsJoG5MwNq/hrd7pLdjOWgs4CLlNV6L+3rvhhYt+e0QUeh9QrZFUfhXxezlfYfP36WQIBETANBgkqhkiG9w0BAQsFAAOBwQBtZqvROSfV1znZws9h6M749g1HRpm3vub3RKAZOWfqP2Qag2ML+BjAqEIH1SAaQSZlFbKRsKM2Bp/QpG5ByshwrxoS9ausPNynulMA7dEPvWOExfqYO9Vj/0ejxwAmilseKrVfv333yvcgVRNRqP/LMxqe/8Hw3Ax+Ul83usIZLQ5m4sW9/IUVwlDLk31ddIkPVpx2USKL9eVDXjVhIl7itgJxPyG0wc0I9Ad/ZWy/Dbbilwz1tHcZSZsxSdNFW+k=</ds:X509Certificate></ds:X509Data></ds:KeyInfo>'
47-
48-
bind: # what should the mock idp bind to? (usually 0.0.0.0 or 127.0.0.1)
49-
host: 127.0.0.1
50-
port: 9001
51-
connect: # where should integration tests send their requests?
52-
host: 127.0.0.1
53-
port: 9001
54-
privateKey: test-integration/resources/key.pem
55-
publicKey: test-integration/resources/pubkey.pem
56-
cert: test-integration/resources/cert.pem

services/spar/package.yaml

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ dependencies:
2323
- bytestring-conversion
2424
- case-insensitive
2525
- cassandra-util
26-
- connection
2726
- containers
2827
- cookie
2928
- cryptonite
@@ -40,7 +39,7 @@ dependencies:
4039
- mtl
4140
- optparse-applicative
4241
- raw-strings-qq
43-
- saml2-web-sso >= 0.9
42+
- saml2-web-sso >= 0.11
4443
- scientific
4544
- servant
4645
- servant-multipart
@@ -51,7 +50,6 @@ dependencies:
5150
- text
5251
- time
5352
- tinylog
54-
- tls
5553
- transformers
5654
- types-common
5755
- uri-bytestring

services/spar/schema/src/V2.hs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,10 @@ import Text.RawString.QQ
1010
migration :: Migration
1111
migration = Migration 2 "Add extra idp keys set" $ do
1212

13-
void $ schema' [r| ALTER TABLE idp ADD extra_public_keys list<blob>; |]
13+
void $ schema' [r|
14+
ALTER TABLE idp DROP metadata;
15+
|]
16+
17+
void $ schema' [r|
18+
ALTER TABLE idp ADD extra_public_keys list<blob>;
19+
|]

services/spar/spar.integration.yaml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,5 +35,3 @@ maxttlAuthreq: 5 # seconds. don't set this too large, it is also the run time
3535
maxttlAuthresp: 7200 # seconds. do not set this to 1h or less, as that is what the mock idp wants.
3636

3737
logNetStrings: False # log using netstrings encoding (see http://cr.yp.to/proto/netstrings.txt)
38-
39-
tlsDisableCertValidation: True # only for testing! in production, this must be set to 'False'!

services/spar/src/Spar/API.hs

Lines changed: 8 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -32,15 +32,9 @@ module Spar.API
3232
, IdpDelete
3333
) where
3434

35-
import Bilge
3635
import Brig.Types.User as Brig
37-
import Control.Exception
3836
import Control.Monad.Except
39-
import Control.Monad.Reader
40-
import Data.Either
41-
import Data.EitherR (fmapL)
4237
import Data.Id
43-
import Data.List.NonEmpty (NonEmpty((:|)))
4438
import Data.Maybe (isJust, fromJust)
4539
import Data.Proxy
4640
import Data.String.Conversions
@@ -61,14 +55,9 @@ import Spar.Options
6155
import Spar.Types
6256

6357
import qualified Data.ByteString as SBS
64-
import qualified Data.X509 as X509
65-
import qualified Network.HTTP.Client as Rq
6658
import qualified SAML2.WebSSO as SAML
6759
import qualified Spar.Data as Data
6860
import qualified Spar.Intra.Brig as Intra
69-
import qualified Text.XML as XML
70-
import qualified Text.XML.DSig as SAML
71-
import qualified Text.XML.Util as SAML
7261
import qualified URI.ByteString as URI
7362

7463

@@ -108,7 +97,7 @@ type APIAuthResp = "sso" :> "finalize-login" :> SAML.APIAuthResp
10897

10998
type IdpGet = Header "Z-User" UserId :> "identity-providers" :> Capture "id" SAML.IdPId :> Get '[JSON] IdP
11099
type IdpGetAll = Header "Z-User" UserId :> "identity-providers" :> Get '[JSON] IdPList
111-
type IdpCreate = Header "Z-User" UserId :> "identity-providers" :> ReqBody '[JSON] SAML.NewIdP :> PostCreated '[JSON] IdP
100+
type IdpCreate = Header "Z-User" UserId :> "identity-providers" :> ReqBody '[SAML.XML] SAML.IdPMetadata :> PostCreated '[JSON] IdP
112101
type IdpDelete = Header "Z-User" UserId :> "identity-providers" :> Capture "id" SAML.IdPId :> DeleteNoContent '[JSON] NoContent
113102

114103
-- FUTUREWORK (thanks jschaul): In a more recent version of servant, using Header '[Strict] becomes
@@ -184,10 +173,10 @@ idpDelete zusr idpid = withDebugLog "idpDelete" (const Nothing) $ do
184173
return NoContent
185174

186175
-- | We generate a new UUID for each IdP used as IdPConfig's path, thereby ensuring uniqueness.
187-
idpCreate :: ZUsr -> SAML.NewIdP -> Spar IdP
188-
idpCreate zusr newIdP = withDebugLog "idpCreate" (Just . show . (^. SAML.idpId)) $ do
176+
idpCreate :: ZUsr -> SAML.IdPMetadata -> Spar IdP
177+
idpCreate zusr idpmeta = withDebugLog "idpCreate" (Just . show . (^. SAML.idpId)) $ do
189178
teamid <- getZUsrOwnedTeam zusr
190-
idp <- validateNewIdP newIdP teamid
179+
idp <- validateNewIdP idpmeta teamid
191180
SAML.storeIdPConfig idp
192181
pure idp
193182

@@ -217,17 +206,15 @@ getZUsrOwnedTeam (Just uid) = do
217206
Just teamid -> teamid <$ Intra.assertIsTeamOwner uid teamid
218207

219208

220-
-- | FUTUREWORK: much of this function could move to the saml2-web-sso package.
209+
-- | FUTUREWORK: move this to the saml2-web-sso package. (same probably goes for get, create,
210+
-- update, delete of idps.)
221211
validateNewIdP :: forall m. (HasCallStack, m ~ Spar)
222-
=> SAML.NewIdP -> TeamId -> m IdP
223-
validateNewIdP (SAML.NewIdP _idpMetadataURI metadataPublicKey) _idpeTeam = do
212+
=> SAML.IdPMetadata -> TeamId -> m IdP
213+
validateNewIdP _idpMetadata _idpeTeam = do
224214
_idpId <- SAML.IdPId <$> SAML.createUUID
225215
_idpeSPInfo <- wrapMonadClientWithEnv $ Data.getSPInfo _idpId
226216
let _idpExtraInfo = IdPExtra { _idpeTeam, _idpeSPInfo }
227217

228-
_idpMetadata :: SAML.IdPMetadata
229-
<- fetchMetadata _idpMetadataURI metadataPublicKey
230-
231218
wrapMonadClient (Data.getIdPIdByIssuer (_idpMetadata ^. SAML.edIssuer)) >>= \case
232219
Nothing -> pure ()
233220
Just _ -> throwSpar SparNewIdPAlreadyInUse
@@ -244,40 +231,6 @@ validateNewIdP (SAML.NewIdP _idpMetadataURI metadataPublicKey) _idpeTeam = do
244231

245232
pure SAML.IdPConfig {..}
246233

247-
fetchMetadata :: forall m. (HasCallStack, m ~ Spar) => URI.URI -> X509.SignedCertificate -> m SAML.IdPMetadata
248-
fetchMetadata metadataUrl pubkey = do
249-
let fetch :: URI.URI -> (Request -> Request) -> m (Bilge.Response (Maybe LBS))
250-
fetch uri modify = do
251-
req <- uri2req uri
252-
ntm (httpLbs req modify)
253-
254-
uri2req :: URI.URI -> m Request
255-
uri2req = either (throwSpar . SparNewIdPBadMetaUrl . cs . show) pure
256-
. Rq.parseRequest . cs . SAML.renderURI
257-
258-
-- natural transformation into 'm'. needed for the http client that fetches the metadata url.
259-
-- if 'IO' throws an exception, we capture it with 'try' and re-throw it inside 'm', which
260-
-- yields much nicer client errors and logs.
261-
ntm :: forall a. Http a -> m a
262-
ntm (HttpT action) = do
263-
mgr :: Manager <- getManager
264-
result :: Either SomeException a <- liftIO . try $ runReaderT action mgr
265-
either (throwSpar . SparNewIdPBadMetaUrl . cs . show @SomeException) pure result
266-
267-
metaResp :: Bilge.Response (Maybe LBS)
268-
<- fetch metadataUrl (method GET . expect2xx)
269-
metaBody :: LBS
270-
<- maybe (throwSpar $ SparNewIdPBadMetaUrl "No body in response.") pure $ responseBody metaResp
271-
when (isLeft $ do
272-
creds <- SAML.certToCreds pubkey
273-
SAML.verifyRoot (creds :| []) metaBody) $ do
274-
throwSpar SparNewIdPBadMetaSig
275-
meta :: SAML.IdPMetadata
276-
<- either (throwSpar . SparNewIdPBadMetaUrl . cs) pure $ do
277-
XML.Document _ el _ <- fmapL show $ XML.parseLBS XML.def metaBody
278-
SAML.parseIdPMetadata el
279-
pure meta
280-
281234

282235
-- | Type families to convert spar's 'API' type into an "outside-world-view" API type
283236
-- to expose as swagger docs intended to be used by client developers.

services/spar/src/Spar/API/Swagger.hs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -121,9 +121,6 @@ instance ToSchema SAML.IdPMetadata where
121121
instance ToSchema IdPList where
122122
declareNamedSchema = genericDeclareNamedSchema samlSchemaOptions
123123

124-
instance ToSchema SAML.NewIdP where
125-
declareNamedSchema = genericDeclareNamedSchema samlSchemaOptions
126-
127124
instance ToSchema (SAML.ID SAML.AuthnRequest) where
128125
declareNamedSchema = genericDeclareNamedSchema samlSchemaOptions
129126

services/spar/src/Spar/Data.hs

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -170,15 +170,14 @@ getUser (SAML.UserRef tenant subject) = fmap runIdentity <$>
170170
----------------------------------------------------------------------
171171
-- idp
172172

173-
type IdPConfigRow = (SAML.IdPId, URI, SAML.Issuer, URI, SignedCertificate, [SignedCertificate], TeamId)
173+
type IdPConfigRow = (SAML.IdPId, SAML.Issuer, URI, SignedCertificate, [SignedCertificate], TeamId)
174174

175175
storeIdPConfig :: (HasCallStack, MonadClient m) => SAML.IdPConfig IdPExtra -> m ()
176176
storeIdPConfig idp = retry x5 . batch $ do
177177
setType BatchLogged
178178
setConsistency Quorum
179179
addPrepQuery ins
180180
( idp ^. SAML.idpId
181-
, idp ^. SAML.idpMetadataURI
182181
, idp ^. SAML.idpIssuer
183182
, idp ^. SAML.idpRequestUri
184183
, NL.head (idp ^. SAML.idpPublicKeys)
@@ -196,7 +195,7 @@ storeIdPConfig idp = retry x5 . batch $ do
196195
)
197196
where
198197
ins :: PrepQuery W IdPConfigRow ()
199-
ins = "INSERT INTO idp (idp, metadata, issuer, request_uri, public_key, extra_public_keys, team) VALUES (?, ?, ?, ?, ?, ?, ?)"
198+
ins = "INSERT INTO idp (idp, issuer, request_uri, public_key, extra_public_keys, team) VALUES (?, ?, ?, ?, ?, ?)"
200199

201200
byIssuer :: PrepQuery W (SAML.IdPId, SAML.Issuer) ()
202201
byIssuer = "INSERT INTO issuer_idp (idp, issuer) VALUES (?, ?)"
@@ -217,7 +216,6 @@ getIdPConfig idpid =
217216
where
218217
toIdp :: IdPConfigRow -> m IdP
219218
toIdp ( _idpId
220-
, _idpMetadataURI
221219
-- metadata
222220
, _edIssuer
223221
, _edRequestURI
@@ -233,7 +231,7 @@ getIdPConfig idpid =
233231
pure $ SAML.IdPConfig {..}
234232

235233
sel :: PrepQuery R (Identity SAML.IdPId) IdPConfigRow
236-
sel = "SELECT idp, metadata, issuer, request_uri, public_key, extra_public_keys, team FROM idp WHERE idp = ?"
234+
sel = "SELECT idp, issuer, request_uri, public_key, extra_public_keys, team FROM idp WHERE idp = ?"
237235

238236
getIdPConfigByIssuer
239237
:: (HasCallStack, MonadClient m, MonadReader Env m)

services/spar/src/Spar/Options.hs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@ data Opts = Opts
3838
, maxttlAuthresp :: !(TTL "authresp")
3939
, discoUrl :: !(Maybe Text) -- Wire/AWS specific; optional; used to discover cassandra instance IPs using describe-instances
4040
, logNetStrings :: !Bool
41-
, tlsDisableCertValidation :: !Bool -- always set to 'False' in production! see 'sparManager'.
4241
-- , optSettings :: !Settings -- (nothing yet; see other services for what belongs in here.)
4342
}
4443
deriving (Show, Generic)

services/spar/src/Spar/Run.hs

Lines changed: 1 addition & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ import Data.Metrics (metrics)
2525
import Data.String.Conversions
2626
import Data.String (fromString)
2727
import Lens.Micro
28-
import Network.HTTP.Client (responseTimeoutMicro)
2928
import Network.Wai (Application)
3029
import Network.Wai.Utilities.Request (lookupRequestId)
3130
import Spar.API
@@ -41,9 +40,6 @@ import Util.Options (epHost, epPort)
4140

4241
import qualified Cassandra.Schema as Cas
4342
import qualified Cassandra.Settings as Cas
44-
import qualified Network.Connection as TLS
45-
import qualified Network.HTTP.Client.TLS as TLS
46-
import qualified Network.TLS.Extra.Cipher as TLS
4743
import qualified Network.Wai.Handler.Warp as Warp
4844
import qualified Network.Wai.Utilities.Server as WU
4945
import qualified SAML2.WebSSO as SAML
@@ -99,7 +95,7 @@ runServer sparCtxOpts = do
9995
let settings = Warp.defaultSettings
10096
& Warp.setHost (fromString $ sparCtxOpts ^. to saml . SAML.cfgSPHost)
10197
. Warp.setPort (sparCtxOpts ^. to saml . SAML.cfgSPPort)
102-
sparCtxHttpManager <- sparManager (tlsDisableCertValidation sparCtxOpts)
98+
sparCtxHttpManager <- newManager defaultManagerSettings
10399
let sparCtxHttpBrig = Bilge.host (sparCtxOpts ^. to brig . epHost . to cs)
104100
. Bilge.port (sparCtxOpts ^. to brig . epPort)
105101
$ Bilge.empty
@@ -113,26 +109,6 @@ runServer sparCtxOpts = do
113109
$ \sparCtxRequestId -> app Env {..}
114110
WU.runSettingsWithShutdown settings wrappedApp 5
115111

116-
-- | Create a TLS-capabable manager for fetching metadata from IdPs.
117-
--
118-
-- NB: In integration tests, we turn certificate validation off. This is not ideal, but the
119-
-- alternative would be to register a mock certificate with the production spar service, which is
120-
-- not trivial and thus not risk-free either:
121-
--
122-
-- * https://stackoverflow.com/questions/25833305/accepting-specific-certificate-with-http-client-tls-or-tls
123-
-- * https://stackoverflow.com/questions/40081508/how-to-provide-a-client-certificate-to-http-client-tls#40082394
124-
sparManager :: Bool -> IO Manager
125-
sparManager disableCertificateValidation = newManager (TLS.mkManagerSettings tlss Nothing)
126-
{ managerResponseTimeout = responseTimeoutMicro (10 * 1000 * 1000)
127-
}
128-
where
129-
tlss = TLS.TLSSettingsSimpleWithCiphers
130-
{ TLS.settingDisableCertificateValidation = disableCertificateValidation
131-
, TLS.settingSupportedCiphers = TLS.ciphersuite_default -- this is why we are pinned to https://github.com/vincenthz/hs-connection/pull/36
132-
, TLS.settingDisableSession = False
133-
, TLS.settingUseServerName = False
134-
}
135-
136112
lookupRequestIdMiddleware :: (RequestId -> Application) -> Application
137113
lookupRequestIdMiddleware mkapp req cont = do
138114
let reqid = maybe mempty RequestId $ lookupRequestId req

0 commit comments

Comments
 (0)