Skip to content

Commit

Permalink
Merge pull request #110 from quan-to/develop
Browse files Browse the repository at this point in the history
[RELEASE] | V1.5.1
  • Loading branch information
racerxdl authored May 25, 2021
2 parents 0d48045 + 306b5d7 commit e57d7b9
Show file tree
Hide file tree
Showing 10 changed files with 140 additions and 44 deletions.
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,19 @@ These are the Environment Variables that you can set to manage the webserver:
* `MODE` => Mode of remote-signer (`single_key`, `default`)
* `ON_DEMAND_KEY_LOAD` => Do not attempt to load all keys from keybackend. Load them as needed (defaults `false`)
* `ENABLE_SWAGGER` => Enables swagger on `/swagger` for Remote Signer. (defaults to `true`)
* `SET_EXPOSED_SERVICES` => Enable only services described by `EXPOSED_SERVICES`
* `EXPOSED_SERVICES` => List of comma separated values with the services that should be exposed
* `__internal` => `/__internal` endpoint (needed for cluster key password sharing)
* `gpg` => `/gpg` endpoint
* `tests` => `/tests` endpoint
* `keyRing` => `/keyRing` endpoint
* `sks` => `/sks` endpoint
* `fieldCipher` => `/fieldCipher` endpoint
* `pks` => `/pks` endpoint
* `agent` => `/agent` endpoint
* `agentAdmin` => `/agentAdmin` endpoint
* `graphiql` => `/graphiql` and `/assets` endpoints
* `agent` => `/agent` endpoint

## Caching Configuration

Expand All @@ -69,6 +82,7 @@ To enable, use the following environment variables:
* `AGENT_KEY_FINGERPRINT` => Default Key FingerPrint for Agent
* `AGENT_BYPASS_LOGIN` => If the Login for using Quanto Agent should be bypassed. *DO NOT USE THIS IN EXPOSED REMOTESIGNER*
* `AGENT_EXTERNAL_URL` => External URL used by GraphiQL to access agent. Defaults to `/agent`
* `AGENT_FORCE_URL` => If true, forces agent URL to be the value defined by `AGENT_EXTERNAL_URL`
* `AGENTADMIN_EXTERNAL_URL` => External URL used by GraphiQL to access agent admin. Defaults to `/agentAdmin`
* `READONLY_KEYPATH` => If the keypath is readonly. If `true` then it will create a temporary folder in `/tmp` and copy all keys to there so it can work over it.
* `HTTP_PORT` => HTTP Port that Remote Signer will run
Expand Down
4 changes: 3 additions & 1 deletion build/remote-signer/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ ENV KEY_PREFIX ""
ENV MAX_KEYRING_CACHE_SIZE "1000"
ENV KEYS_BASE64_ENCODED "true"
ENV READONLY_KEYPATH "false"
ENV SET_EXPOSED_SERVICES "false"
ENV EXPOSED_SERVICES ""

# Cluster Mode
ENV MASTER_GPG_KEY_PATH ""
Expand All @@ -73,7 +75,7 @@ ENV AGENT_KEY_FINGERPRINT ""
ENV AGENT_BYPASS_LOGIN "false"
ENV AGENT_EXTERNAL_URL "/agent"
ENV AGENTADMIN_EXTERNAL_URL "/agentAdmin"

ENV AGENT_FORCE_URL "false"

# Database
ENV ENABLE_DATABASE "false"
Expand Down
12 changes: 8 additions & 4 deletions internal/agent/DatabaseAuthManager.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,17 +76,21 @@ func (ram *DatabaseAuthManager) LoginAuth(username, password string) (fingerPrin
um, err := ram.dbAuth.GetUser(username)

if err != nil || um == nil {
return "", "", fmt.Errorf("invalid username or password")
// To avoid timing attack pretend that we have a valid user
um = &models.User{ID: invalidUserId}
}

hash, err := base64.StdEncoding.DecodeString(um.Password)
if err != nil {
ram.log.Error("Error decoding hash: %v", err)
return "", "", fmt.Errorf("invalid username or password")
// Same here, let's pretend we have a hash
hash = []byte(bcryptFallbackHash)
// Unset password to avoid knowing the correct password from the hash above
password = ""
}

err = bcrypt.CompareHashAndPassword(hash, []byte(password))
if err != nil {
err = bcrypt.CompareHashAndPassword(hash, []byte(password)) // Execute even if we know it's invalid
if um.ID == invalidUserId || err != nil { // Now we check for a invalid user
return "", "", fmt.Errorf("invalid username or password")
}

Expand Down
12 changes: 8 additions & 4 deletions internal/agent/JSONAuthManager.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,17 +104,21 @@ func (jam *JSONAuthManager) LoginAuth(username, password string) (fingerPrint, f
user, exists := jam.users[username]

if !exists {
return "", "", fmt.Errorf("invalid username or password")
// To avoid timing attack pretend that we have a valid user
user = jsonUser{Username: invalidUserId}
}

hash, err := base64.StdEncoding.DecodeString(user.Password)
if err != nil {
jam.log.Error("Error decoding hash: %v", err)
return "", "", fmt.Errorf("invalid username or password")
// Same here, let's pretend we have a hash
hash = []byte(bcryptFallbackHash)
// Unset password to avoid knowing the correct password from the hash above
password = ""
}

err = bcrypt.CompareHashAndPassword(hash, []byte(password))
if err != nil {
err = bcrypt.CompareHashAndPassword(hash, []byte(password)) // Execute even if we know it's invalid
if user.Username == invalidUserId || err != nil {
return "", "", fmt.Errorf("invalid username or password")
}

Expand Down
7 changes: 7 additions & 0 deletions internal/agent/TimeAttack.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package agent

// That fallback hash is used only to avoid time-based attacks
// We want a valid hash to be here since we actually want the bcrypt compare to run
// even if afterwards we will discard its result
const bcryptFallbackHash = "$2y$10$ulOzTkNjLpMJI2HS7rq5/eEIX6qLa9JtcKQ9uk8PjYq68ZUsMN5di "
const invalidUserId = "invalid user id"
26 changes: 26 additions & 0 deletions internal/config/configuration.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ var VaultBackend string
var VaultSkipDataType bool
var VaultTokenTTL string
var AgentTargetURL string
var AgentForceURL bool
var AgentTokenExpiration int
var AgentKeyFingerPrint string
var AgentBypassLogin bool
Expand Down Expand Up @@ -73,9 +74,28 @@ var RedisTLSEnabled bool
var RedisMaxLocalObjects int
var RedisLocalObjectTTL time.Duration

var SetExposedServices bool
var ExposedServices []string

// LogFormat allows to configure the output log format
var LogFormat slog.Format

func IsServiceExposed(name string) bool {
if !SetExposedServices {
return true
}

name = strings.ToLower(name)

for _, v := range ExposedServices {
if v == name {
return true
}
}

return false
}

func configDeprecationMessage(userConfig, newConfig string) {
if newConfig != "" {
slog.Warn("The configuration %q is currently deprecated. Please use %q instead.", userConfig, newConfig)
Expand Down Expand Up @@ -183,6 +203,7 @@ func Setup() {
VaultTokenTTL = os.Getenv("VAULT_TOKEN_TTL")
AgentTargetURL = os.Getenv("AGENT_TARGET_URL")
AgentKeyFingerPrint = os.Getenv("AGENT_KEY_FINGERPRINT")
AgentForceURL = os.Getenv("AGENT_FORCE_URL") == "true"
AgentBypassLogin = os.Getenv("AGENT_BYPASS_LOGIN") == "true"
DatabaseTokenManager = os.Getenv("RETHINK_TOKEN_MANAGER") == "true" || os.Getenv("DATABASE_TOKEN_MANAGER") == "true"
DatabaseAuthManager = os.Getenv("RETHINK_AUTH_MANAGER") == "true" || os.Getenv("AUTH_MANAGER") == "true"
Expand Down Expand Up @@ -237,6 +258,9 @@ func Setup() {
RedisMaxLocalObjects = int(v)
}

SetExposedServices = os.Getenv("SET_EXPOSED_SERVICES") == "true"
ExposedServices = strings.Split(os.Getenv("EXPOSED_SERVICES"), ",")

// Set defaults if not defined
if SyslogServer == "" {
SyslogServer = "127.0.0.1"
Expand Down Expand Up @@ -332,9 +356,11 @@ func Setup() {
if Environment == "development" {
slog.SetDebug(true)
QuantoError.EnableStackTrace()
QuantoError.EnableErrorData()
} else {
slog.SetDebug(false)
QuantoError.DisableStackTrace()
QuantoError.DisableErrorData()
}

// Deprecation
Expand Down
2 changes: 1 addition & 1 deletion internal/server/agentProxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ func (proxy *AgentProxy) defaultHandler(w http.ResponseWriter, r *http.Request)

targetURL := config.AgentTargetURL

if h.Get("serverUrl") != "" {
if h.Get("serverUrl") != "" && !config.AgentForceURL {
targetURL = h.Get("serverUrl")
}

Expand Down
4 changes: 2 additions & 2 deletions internal/server/httpmiddlewares_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ func TestSkipEndpointLoggingMiddleware(t *testing.T) {
slog.SetDefaultOutput(&logBuffer)
slog.SetLogFormat(slog.JSON)

mockHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { })
mockHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})

for endpoint := range skipEndpoints {
req := httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://huehuebr.com%s", endpoint), nil)
Expand All @@ -123,4 +123,4 @@ func TestSkipEndpointLoggingMiddleware(t *testing.T) {

slog.SetTestMode()
slog.SetLogFormat(slog.PIPE)
}
}
78 changes: 50 additions & 28 deletions internal/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,34 +95,56 @@ func GenRemoteSignerServerMux(slog slog.Instance, sm interfaces.SecretsManager,

r.Use(LoggingMiddleware)

// Add for /
AddHKPEndpoints(log, dbh, r.PathPrefix("/pks").Subrouter())
ge.AttachHandlers(r.PathPrefix("/gpg").Subrouter())
ie.AttachHandlers(r.PathPrefix("/__internal").Subrouter())
te.AttachHandlers(r.PathPrefix("/tests").Subrouter())
kre.AttachHandlers(r.PathPrefix("/keyRing").Subrouter())
sks.AttachHandlers(r.PathPrefix("/sks").Subrouter())
jfc.AttachHandlers(r.PathPrefix("/fieldCipher").Subrouter())

// Add for /remoteSigner
AddHKPEndpoints(log, dbh, r.PathPrefix("/remoteSigner/pks").Subrouter())
ge.AttachHandlers(r.PathPrefix("/remoteSigner/gpg").Subrouter())
ie.AttachHandlers(r.PathPrefix("/remoteSigner/__internal").Subrouter())
te.AttachHandlers(r.PathPrefix("/remoteSigner/tests").Subrouter())
kre.AttachHandlers(r.PathPrefix("/remoteSigner/keyRing").Subrouter())
sks.AttachHandlers(r.PathPrefix("/remoteSigner/sks").Subrouter())
jfc.AttachHandlers(r.PathPrefix("/remoteSigner/fieldCipher").Subrouter())

// Agent
ap.AddHandlers(r.PathPrefix("/agent").Subrouter())

// Agent Admin
agentAdmin.AddHandlers(r.PathPrefix("/agentAdmin").Subrouter())

// Static GraphiQL
sGql.AttachHandlers(r.PathPrefix("/graphiql").Subrouter())

pages.AddHandlers(r.PathPrefix("/assets").Subrouter())
if config.IsServiceExposed("pks") {
AddHKPEndpoints(log, dbh, r.PathPrefix("/pks").Subrouter())
AddHKPEndpoints(log, dbh, r.PathPrefix("/remoteSigner/pks").Subrouter())
}

if config.IsServiceExposed("gpg") {
ge.AttachHandlers(r.PathPrefix("/gpg").Subrouter())
ge.AttachHandlers(r.PathPrefix("/remoteSigner/gpg").Subrouter())
}

if config.IsServiceExposed("__internal") {
ie.AttachHandlers(r.PathPrefix("/__internal").Subrouter())
ie.AttachHandlers(r.PathPrefix("/remoteSigner/__internal").Subrouter())
}

if config.IsServiceExposed("tests") {
te.AttachHandlers(r.PathPrefix("/tests").Subrouter())
te.AttachHandlers(r.PathPrefix("/remoteSigner/tests").Subrouter())
}

if config.IsServiceExposed("keyRing") {
kre.AttachHandlers(r.PathPrefix("/keyRing").Subrouter())
kre.AttachHandlers(r.PathPrefix("/remoteSigner/keyRing").Subrouter())
}

if config.IsServiceExposed("sks") {
sks.AttachHandlers(r.PathPrefix("/sks").Subrouter())
sks.AttachHandlers(r.PathPrefix("/remoteSigner/sks").Subrouter())
}

if config.IsServiceExposed("fieldCipher") {
jfc.AttachHandlers(r.PathPrefix("/fieldCipher").Subrouter())
jfc.AttachHandlers(r.PathPrefix("/remoteSigner/fieldCipher").Subrouter())
}

if config.IsServiceExposed("agent") {
// Agent
ap.AddHandlers(r.PathPrefix("/agent").Subrouter())
}
if config.IsServiceExposed("agentAdmin") {
// Agent Admin
agentAdmin.AddHandlers(r.PathPrefix("/agentAdmin").Subrouter())
}

if config.IsServiceExposed("graphiql") {
// Static GraphiQL
sGql.AttachHandlers(r.PathPrefix("/graphiql").Subrouter())

pages.AddHandlers(r.PathPrefix("/assets").Subrouter())
}

// Catch All for unhandled endpoints
r.PathPrefix("/").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
Expand Down
25 changes: 21 additions & 4 deletions pkg/QuantoError/errorobject.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
)

var stackEnabled = true
var errorDataEnabled = true

func EnableStackTrace() {
stackEnabled = true
Expand All @@ -18,6 +19,14 @@ func DisableStackTrace() {
stackEnabled = false
}

func EnableErrorData() {
errorDataEnabled = true
}

func DisableErrorData() {
errorDataEnabled = false
}

//Flag to define if a stack trace is returned in response or not
func ShowStackTrace() bool {
return stackEnabled
Expand All @@ -32,13 +41,19 @@ type ErrorObject struct {
}

func New(errorCode, errorField, message string, errorData interface{}) *ErrorObject {
return &ErrorObject{
eo := &ErrorObject{
ErrorCode: errorCode,
ErrorField: errorField,
ErrorData: errorData,
Message: message,
StackTrace: string(debug.Stack()),
}

if errorDataEnabled {
eo.ErrorData = errorData
}
if stackEnabled {
eo.StackTrace = string(debug.Stack())
}
return eo
}

func (e *ErrorObject) Error() string {
Expand All @@ -59,7 +74,9 @@ func (e *ErrorObject) ToFormattedError() gqlerrors.FormattedError {
if stackEnabled {
baseErr.Extensions["stackTrace"] = e.StackTrace
}
baseErr.Extensions["errorData"] = e.ErrorData
if errorDataEnabled {
baseErr.Extensions["errorData"] = e.ErrorData
}

return baseErr
}
Expand Down

0 comments on commit e57d7b9

Please sign in to comment.