Skip to content

Commit

Permalink
fix gearpump#1247, add authentication for UI
Browse files Browse the repository at this point in the history
  • Loading branch information
clockfly committed Jan 13, 2016
1 parent ba3037a commit 43f4541
Show file tree
Hide file tree
Showing 34 changed files with 1,241 additions and 338 deletions.
3 changes: 3 additions & 0 deletions .travis.yml
Expand Up @@ -2,6 +2,9 @@ language:
- java
- scala
sudo: false
before_script:
- mkdir -p $HOME/.sbt/launchers/0.13.9/
- curl -L -o $HOME/.sbt/launchers/0.13.9/sbt-launch.jar http://dl.bintray.com/typesafe/ivy-releases/org.scala-sbt/sbt-launch/0.13.9/sbt-launch.jar
script:
- echo "TRAVIS_PULL_REQUEST" $TRAVIS_PULL_REQUEST
- echo "TRAVIS_BRANCH" $TRAVIS_BRANCH
Expand Down
38 changes: 38 additions & 0 deletions conf/gear.conf
Expand Up @@ -259,4 +259,42 @@ gearpump {
enabled = true
}
}

## Security related settings
security {

## Whether enable authentication for UI Server
ui-authentication-enabled = true

## What authenticator to use. The class must implement interface
## io.gearpump.security.Authenticator
ui-authenticator = "io.gearpump.security.ConfigFileBasedAuthenticator"

## Configuration options for authenticator io.gearpump.security.ConfigFileBasedAuthenticator
config-file-based-authenticator = {
## Format: username = "password_hash_value"
## password_hash_value can be generated by running shell tool:
## bin/gear io.gearpump.security.PasswordUtil -password <your raw password>

## Admin users have super permission to do everything
admins = {
## Default Admin. Username: admin, password: admin
## !!! Please replace this builtin account for production cluster for security reason. !!!
"admin" = "AeGxGOxlU8QENdOXejCeLxy+isrCv0TrS37HwA=="
}

## normal user have special permission for certain operations.
users = {
## "user" = "AeGxGOxlU8QENdOXejCeLxy+isrCv0TrS37HwA=="
}

## Guest user can only view the UI with minimum permission. With no permission to submit/change/kill
## a running application.
guests = {
## Default guest. Username: guest, Password: guest
## !!! Please replace this builtin account for production cluster for security reason. !!!
"guest" = "ws+2Dy/FHX4cBb3uKGTR64kZWlWbC91XZRRoew=="
}
}
}
}
60 changes: 58 additions & 2 deletions core/src/main/resources/reference.conf
Expand Up @@ -244,6 +244,44 @@ gearpump {
single-thread-dispatcher {
type = PinnedDispatcher
}

## Security related settings
security {

## Whether enable authentication for UI Server
ui-authentication-enabled = false

## What authenticator to use. The class must implement interface
## io.gearpump.security.Authenticator
ui-authenticator = "io.gearpump.security.ConfigFileBasedAuthenticator"

## Configuration options for authenticator io.gearpump.security.ConfigFileBasedAuthenticator
config-file-based-authenticator = {
## Format: username = "password_hash_value"
## password_hash_value can be generated by running shell tool:
## bin/gear io.gearpump.security.PasswordUtil -password <your raw password>

## Admin users have super permission to do everything
admins = {
## Default Admin. Username: admin, password: admin
## !!! Please replace this builtin account for production cluster for security reason. !!!
"admin" = "AeGxGOxlU8QENdOXejCeLxy+isrCv0TrS37HwA=="
}

## normal user have special permission for certain operations.
users = {
## "user" = "AeGxGOxlU8QENdOXejCeLxy+isrCv0TrS37HwA=="
}

## Guest user can only view the UI with minimum permission. With no permission to submit/change/kill
## a running application.
guests = {
## Default guest. Username: guest, Password: guest
## !!! Please replace this builtin account for production cluster for security reason. !!!
"guest" = "ws+2Dy/FHX4cBb3uKGTR64kZWlWbC91XZRRoew=="
}
}
}
}

### Akka system configuration for master nodes
Expand Down Expand Up @@ -319,19 +357,37 @@ base {
akka.scheduler.tick-duration = 1

akka {

http {
client {
parsing {
max-content-length = 2048m
}
}
server {
remote-address-header = on
parsing {
max-content-length = 2048m
illegal-header-warnings = off
}
}

## Akka-http session related settings
session {
clientSession {
cookie {
name = "gearpump_token"
## domain = "..."
path = "/"
## maxAge = 0
secure = false
httpOnly = true
}

## Session lifetime.
maxAgeSeconds = 3600
encryptData = true
}
}
}

test {
Expand Down Expand Up @@ -411,4 +467,4 @@ windows {
### On windows, the value must be larger than 10ms, check
### https://github.com/akka/akka/blob/master/akka-actor/src/main/scala/akka/actor/Scheduler.scala#L204
akka.scheduler.tick-duration = 10
}
}
67 changes: 67 additions & 0 deletions core/src/main/scala/io/gearpump/security/Authenticator.scala
@@ -0,0 +1,67 @@
/*
* 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.Authenticator.AuthenticationResult

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]
}

object Authenticator {

trait AuthenticationResult {

def authenticated: Boolean

def permissionLevel: Int
}

val UnAuthenticated = new AuthenticationResult{
override val authenticated = false
override val permissionLevel = -1
}

val Guest = new AuthenticationResult{
override val authenticated = true
override val permissionLevel = 1000
}

val User = new AuthenticationResult{
override val authenticated = true
override val permissionLevel = 1000 + Guest.permissionLevel
}

val Admin = new AuthenticationResult{
override val authenticated = true
override val permissionLevel = 1000 + User.permissionLevel
}
}
@@ -0,0 +1,98 @@
/*
* 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.Authenticator.AuthenticationResult
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 + "." + "admins"
private val USERS = ROOT + "." + "users"
private val GUESTS = ROOT + "." + "guests"

private case class Credentials(admins: Map[String, String], users: Map[String, String], guests: Map[String, String]) {
def verify(user: String, password: String): AuthenticationResult = {
if (admins.contains(user)) {
Authenticator.Admin
} else if (users.contains(user)) {
Authenticator.User
} else if (guests.contains(user)) {
Authenticator.Guest
} else {
Authenticator.UnAuthenticated
}
}

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 three categories of users: admins, users, and guests.
* admins have unlimited permission, like shutdown a cluster, add/remove machines.
* users have limited permission to submit an application and etc..
* guests can not submit/kill applications, but can view the application status.
*
* 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 users = configToMap(config, USERS)
val guests = configToMap(config, GUESTS)
new Credentials(admins, users, 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 }
}
}

0 comments on commit 43f4541

Please sign in to comment.