TheHive
TheHive copied to clipboard
[Bug] SSO with Azure AD times out
Request Type
Bug
Work Environment
Question | Answer |
---|---|
OS version (server) | Ubuntu 20.04 |
OS version (client) | Ubuntu 20.04 |
Virtualized Env. | True |
Dedicated RAM | 16 GB |
vCPU | 4 |
TheHive version / git hash | 4.1.11-1 |
Package Type | Docker |
Database | Cassandra |
Index type | Lucene |
Attachments storage | Local |
Browser type & version | Firefox 78.15 |
Problem Description
I was trying to set up Azure AD SSO for TheHive following the official docs and added the following snippet to application.conf
:
auth {
providers = [
{name: session}
{name: basic, realm: thehive}
{name: local}
{name: key}
{
name: oauth2
clientId: "<my-client-id>"
clientSecret: "<my-client-secret>"
redirectUri: "http://localhost:9050/api/ssoLogin"
responseType: code
grantType: "authorization_code"
authorizationUrl: "https://login.microsoftonline.com/<my-tenant-id>/oauth2/v2.0/authorize"
authorizationHeader: "Bearer "
tokenUrl: "https://login.microsoftonline.com/<my-tenant-id>/oauth2/v2.0/token"
userUrl: "https://graph.microsoft.com/v1.0/me"
scope: ["User.Read"]
userIdField: "mail"
}
]
}
I do not know if there is a typo in the
authorizationHeader
field's"Bearer "
value, since it has a trailing space, so I tried both with and without a space -> same error
I've created an app registration in the Azure portal and configured the redirect URL to match the one in the application config. The SSO button shows up at the login page however when I try to log in using it the token verification request times out. This is the captured network traffic after hitting the button:
302 GET https://thehive.test/api/ssoLogin
200 GET https://login.microsoftonline.com/<my-tenant-id>/oauth2/v2.0/authorize?state=0cbaf0de-...&scope=User.Read&redirect_uri=https%3A%2F%2Fthehive.test...%2Fapi%2FssoLogin&client_id=<my-client-id>&response_type=code
303 GET https://thehive.test/api/ssoLogin?code=0.ARA...&state=0cbaf0d...&session_state=38d45...#
308 GET https://thehive.test/?error=OAuth2+token+verification+failure+Request+timeout+to+login.microsoftonline.com%3A443+after+120000+ms
The container is deployed behind a proxy, however if I use curl to contact https://login.microsoftonline.com
I get an OK response instead of a timeout.
What am I missing?
Turns out that TheHive doesn't respect system proxies so I had to add the below options to the application call:
-Dhttp.proxyHost=<my-proxy-host> \
-Dhttp.proxyPort=<my-proxy-port> \
-Dhttps.proxyHost=<my-proxy-host> \
-Dhttps.proxyPort=<my-proxy-port> \
-Dhttp.nonProxyHosts="localhost|127.0.0.1|<other-non-proxy-hosts...>" # note the quotes around the list of hosts
I tried to add these configs to application.conf
but I can't seem to find the right keys, tried as simple as http.proxyHost: <my-proxy-host>
and play.http.proxyHost: <my-proxy-host>
but none of them worked.
However I'd like it better to keep these options within the application config if possible, any clues?
Hi,
I have a similar problem as you with Google and without proxy. I changed the two WS calls in /opt/TheHive/ScalliGraph/core/src/main/scala/org/thp/scalligraph/auth/OAuth2Srv.scala by curl call for test.
Since then it has been working fine.
/**
* Querying the OAuth2 server for a token
* @param code the previously obtained code
* @return
*/
private def getAuthTokenFromCode(code: String, state: String): Future[String] = {
logger.trace(s"""
|Request to ${OAuth2Config.tokenUrl} with
| code: $code
| grant_type: ${OAuth2Config.grantType}
| client_secret: ${OAuth2Config.clientSecret}
| redirect_uri: ${OAuth2Config.redirectUri}
| client_id: ${OAuth2Config.clientId}
| state: $state
|""".stripMargin)
WSClient
.url(OAuth2Config.tokenUrl)
.withHttpHeaders("Accept" -> "application/json")
.post(
Map(
"code" -> code,
"grant_type" -> OAuth2Config.grantType.toString,
"client_secret" -> OAuth2Config.clientSecret,
"redirect_uri" -> OAuth2Config.redirectUri,
"client_id" -> OAuth2Config.clientId,
"state" -> state
)
)
.transform {
case Success(r) if r.status == 200 => Success((r.json \ "access_token").asOpt[String].getOrElse(""))
case Failure(error) => Failure(AuthenticationError(s"OAuth2 token verification failure ${error.getMessage}"))
case Success(r) => Failure(AuthenticationError(s"OAuth2/token unexpected response from server (${r.status} ${r.statusText})"))
}
}
/**
* Client query for user data with OAuth2 token
* @param token the token
* @return
*/
private def getUserData(token: String): Future[JsObject] = {
logger.trace(s"Request to ${OAuth2Config.userUrl} with authorization header: ${OAuth2Config.authorizationHeader} $token")
WSClient
.url(OAuth2Config.userUrl)
.addHttpHeaders("Authorization" -> s"${OAuth2Config.authorizationHeader} $token")
.get()
.transform {
case Success(r) if r.status == 200 => Success(r.json.as[JsObject])
case Failure(error) => Failure(AuthenticationError(s"OAuth2 user data fetch failure ${error.getMessage}"))
case Success(r) => Failure(AuthenticationError(s"OAuth2/userinfo unexpected response from server (${r.status} ${r.statusText})"))
}
}
/**
* Querying the OAuth2 server for a token
* @param code the previously obtained code
* @return
*/
private def getAuthTokenFromCode(code: String, state: String): Future[String] = {
logger.trace(s" curl ${OAuth2Config.tokenUrl} -X POST -d code=$code -d grant_type=${OAuth2Config.grantType} -d client_secret=${OAuth2Config.clientSecret} -d redirect_uri=${OAuth2Config.redirectUri} -d client_id=${OAuth2Config.clientId} -d state=$state")
val solr = Seq("curl", OAuth2Config.tokenUrl, "-X", "POST", "-H", "application/json", "-d", "code=" + code , "-d", "grant_type="+OAuth2Config.grantType, "-d", "client_secret="+OAuth2Config.clientSecret,"-d", "redirect_uri="+OAuth2Config.redirectUri, "-d", "client_id="+OAuth2Config.clientId, "-d", "state=" + state)
val res = solr.!!
val json = Json.parse(res)
Future((json \ "access_token").asOpt[String].getOrElse(""))
}
/**
* Client query for user data with OAuth2 token
* @param token the token
* @return
*/
private def getUserData(token: String): Future[JsObject] = {
logger.trace(s"Request to ${OAuth2Config.userUrl} with authorization header: ${OAuth2Config.authorizationHeader} $token")
val solr = Seq("curl", OAuth2Config.userUrl, "-X", "GET", "-H", "Authorization: " + s"${OAuth2Config.authorizationHeader} $token")
val res = solr.!!
logger.trace(s"Result is $res")
val json = Json.parse(res)
Future(json.as[JsObject])
}