forked from gearpump/gearpump
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix gearpump#1247, add authentication for UI
- Loading branch information
Showing
34 changed files
with
1,218 additions
and
360 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
45 changes: 45 additions & 0 deletions
45
core/src/main/scala/io/gearpump/security/Authenticator.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
/* | ||
* Licensed to the Apache Software Foundation (ASF) under one | ||
* or more contributor license agreements. See the NOTICE file | ||
* distributed with this work for additional information | ||
* regarding copyright ownership. The ASF licenses this file | ||
* to you under the Apache License, Version 2.0 (the | ||
* "License"); you may not use this file except in compliance | ||
* with the License. You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package io.gearpump.security | ||
|
||
import scala.concurrent.{ExecutionContext, Future} | ||
|
||
|
||
/** | ||
* Authenticator for UI dashboard. | ||
* | ||
* Sub Class must implement a constructor with signature like this: | ||
* this(config: Config) | ||
* | ||
*/ | ||
trait Authenticator { | ||
|
||
// TODO: Change the signature to return more attributes of user | ||
// credentials... | ||
def authenticate(user: String, password: String, ec: ExecutionContext): Future[AuthenticationResult] | ||
} | ||
|
||
trait AuthenticationResult { | ||
|
||
// Whether current user is a valid user. | ||
def authenticated: Boolean | ||
|
||
// Admin user have unlimited permission | ||
def isAdministrator: Boolean | ||
} |
94 changes: 94 additions & 0 deletions
94
core/src/main/scala/io/gearpump/security/ConfigFileBasedAuthenticator.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
/* | ||
* Licensed to the Apache Software Foundation (ASF) under one | ||
* or more contributor license agreements. See the NOTICE file | ||
* distributed with this work for additional information | ||
* regarding copyright ownership. The ASF licenses this file | ||
* to you under the Apache License, Version 2.0 (the | ||
* "License"); you may not use this file except in compliance | ||
* with the License. You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package io.gearpump.security | ||
|
||
import io.gearpump.security.ConfigFileBasedAuthenticator._ | ||
import com.typesafe.config.Config | ||
import scala.concurrent.{ExecutionContext, Future} | ||
|
||
object ConfigFileBasedAuthenticator { | ||
|
||
private val ROOT = "gearpump.security.config-file-based-authenticator" | ||
private val ADMINS = ROOT + "." + "administrators" | ||
private val GUESTS = ROOT + "." + "guests" | ||
|
||
private class Result(override val authenticated: Boolean, override val isAdministrator: Boolean) | ||
extends AuthenticationResult | ||
|
||
private case class Credentials(admins: Map[String, String], guests: Map[String, String]) { | ||
def verify(user: String, password: String): AuthenticationResult = { | ||
if (admins.contains(user)) { | ||
new Result(verify(user, password, admins), isAdministrator = true) | ||
} else if (guests.contains(user)) { | ||
new Result(verify(user, password, guests), isAdministrator = false) | ||
} else { | ||
new Result(authenticated = false, isAdministrator = false) | ||
} | ||
} | ||
|
||
private def verify(user: String, password: String, map: Map[String, String]): Boolean = { | ||
val storedPass = map(user) | ||
PasswordUtil.verify(password, storedPass) | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* UI dashboard authenticator based on configuration file. | ||
* | ||
* It has two categories of users: administrators, and guests. | ||
* administrators have unlimited permission on the UI, while guests can not submit/kill applications. | ||
* | ||
* see conf/gear.conf section gearpump.security.config-file-based-authenticator to find information | ||
* about how to configure this authenticator. | ||
* | ||
* [Security consideration] | ||
* It will keep one-way sha1 digest of password instead of password itself. The original password is NOT | ||
* kept in any way, so generally it is safe. | ||
* | ||
* digesting flow (from original password to digest): | ||
* random salt byte array of length 8 -> byte array of (salt + sha1(salt, password)) -> base64Encode | ||
* | ||
* verification user input password with stored digest: | ||
* base64Decode -> extract salt -> do sha1(salt, password) -> generate digest: salt + sha1 -> | ||
* compare the generated digest with the stored digest. | ||
* | ||
*/ | ||
class ConfigFileBasedAuthenticator(config: Config) extends Authenticator { | ||
|
||
private val credentials = loadCredentials(config) | ||
|
||
override def authenticate(user: String, password: String, ec: ExecutionContext): Future[AuthenticationResult] = { | ||
implicit val ctx = ec | ||
Future { | ||
credentials.verify(user, password) | ||
} | ||
} | ||
|
||
private def loadCredentials(config: Config): Credentials = { | ||
val admins = configToMap(config, ADMINS) | ||
val guests = configToMap(config, GUESTS) | ||
new Credentials(admins, guests) | ||
} | ||
|
||
private def configToMap(config : Config, path: String) = { | ||
import scala.collection.JavaConverters._ | ||
config.getConfig(path).root.unwrapped.asScala.toMap map { case (k, v) => k -> v.toString } | ||
} | ||
} |
93 changes: 93 additions & 0 deletions
93
core/src/main/scala/io/gearpump/security/PasswordUtil.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
/* | ||
* Licensed to the Apache Software Foundation (ASF) under one | ||
* or more contributor license agreements. See the NOTICE file | ||
* distributed with this work for additional information | ||
* regarding copyright ownership. The ASF licenses this file | ||
* to you under the Apache License, Version 2.0 (the | ||
* "License"); you may not use this file except in compliance | ||
* with the License. You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package io.gearpump.security | ||
|
||
import java.security.MessageDigest | ||
import sun.misc.{BASE64Decoder, BASE64Encoder} | ||
import scala.util.Try | ||
|
||
/** | ||
* Util to verify whether user input password is valid or not. | ||
* It use sha1 to do the digesting. | ||
*/ | ||
object PasswordUtil { | ||
private val SALT_LENGTH = 8 | ||
|
||
/** | ||
* verification user input password with stored digest: | ||
* base64Decode -> extract salt -> do sha1(salt, password) -> | ||
* generate digest: salt + sha1 -> compare the generated digest with the stored digest. | ||
*/ | ||
def verify(password: String, stored: String): Boolean = { | ||
Try { | ||
val decoded = base64Decode(stored) | ||
val salt = new Array[Byte](SALT_LENGTH) | ||
Array.copy(decoded, 0, salt, 0, SALT_LENGTH) | ||
|
||
hash(password, salt) == stored | ||
}.getOrElse(false) | ||
} | ||
/** | ||
* digesting flow (from original password to digest): | ||
* random salt byte array of length 8 -> byte array of (salt + sha1(salt, password)) -> base64Encode | ||
*/ | ||
def hash(password: String): String = { | ||
// Salt generation 64 bits long | ||
val salt = new Array[Byte](SALT_LENGTH) | ||
new java.util.Random().nextBytes(salt) | ||
hash(password, salt) | ||
} | ||
|
||
private def hash(password: String, salt: Array[Byte]): String = { | ||
val digest = MessageDigest.getInstance("SHA-1") | ||
digest.reset() | ||
digest.update(salt) | ||
var input = digest.digest(password.getBytes("UTF-8")) | ||
digest.reset() | ||
input = digest.digest(input) | ||
val withSalt = salt ++ input | ||
base64Encode(withSalt) | ||
} | ||
|
||
private def base64Encode(data: Array[Byte]): String = { | ||
val endecoder = new BASE64Encoder() | ||
endecoder.encode(data) | ||
} | ||
|
||
private def base64Decode(data: String): Array[Byte] = { | ||
val decoder = new BASE64Decoder() | ||
decoder.decodeBuffer(data) | ||
} | ||
|
||
private def help = { | ||
Console.println("usage: gear io.gearpump.security.PasswordUtil -password <your password>") | ||
} | ||
|
||
def main(args: Array[String]): Unit = { | ||
if (args.length != 2 || args(0) != "-password") { | ||
help | ||
} else { | ||
val pass = args(1) | ||
val result = hash(pass) | ||
Console.println("Here is the hashed password") | ||
Console.println("==============================") | ||
Console.println(result) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.