Skip to content

Commit

Permalink
Merge 3422a7a into cd0d937
Browse files Browse the repository at this point in the history
  • Loading branch information
harture committed May 13, 2019
2 parents cd0d937 + 3422a7a commit 4bff54a
Show file tree
Hide file tree
Showing 8 changed files with 180 additions and 15 deletions.
1 change: 1 addition & 0 deletions README.md
Expand Up @@ -70,6 +70,7 @@ CT_BRIDGE_DB_CONFIG_PASSWORD | db-config-password
CT_BRIDGE_INFLUX_USERNAME | influx-username
CT_BRIDGE_INFLUX_PASSWORD | influx-password
CT_BRIDGE_SENTRY_DSN | sentry-dsn
CT_BRIDGE_EVENT_BASIC_AUTH | event-basic-auth-token


## Usage
Expand Down
18 changes: 16 additions & 2 deletions cmd/keycloakb/keycloak_bridge.go
Expand Up @@ -208,6 +208,17 @@ func main() {
}
}

// Security - Basic AuthN token to protect internal/event endpoint
var eventExpectedAuthToken string
{
eventExpectedAuthToken = c.GetString("event-basic-auth-token")

if eventExpectedAuthToken == "" {
logger.Log("msg", "password for event endpoint (event-basic-auth-token) cannot be empty")
return
}
}

// Keycloak client.
var keycloakClient *keycloak.Client
{
Expand Down Expand Up @@ -521,6 +532,7 @@ func main() {
eventHandler = event.MakeHTTPEventHandler(eventEndpoints.Endpoint)
eventHandler = middleware.MakeHTTPCorrelationIDMW(idGenerator, tracer, logger, ComponentName, ComponentID)(eventHandler)
eventHandler = middleware.MakeHTTPTracingMW(tracer, ComponentName, "http_server_event")(eventHandler)
eventHandler = middleware.MakeHTTPBasicAuthenticationMW(eventExpectedAuthToken, logger)(eventHandler)
}
eventSubroute.Handle("/receiver", eventHandler)

Expand Down Expand Up @@ -716,7 +728,8 @@ func config(logger log.Logger) *viper.Viper {
v.SetDefault("account-http-host-port", "0.0.0.0:8866")

// Security - Audience check
v.SetDefault("audience", "")
v.SetDefault("audience-required", "")
v.SetDefault("event-basic-auth-token", "")

// CORS configuration
v.SetDefault("cors-allowed-origins", []string{})
Expand All @@ -726,7 +739,6 @@ func config(logger log.Logger) *viper.Viper {
v.SetDefault("cors-debug", false)

// Keycloak default.
v.SetDefault("keycloak", true)
v.SetDefault("keycloak-api-uri", "http://127.0.0.1:8080")
v.SetDefault("keycloak-oidc-uri", "http://127.0.0.1:8080")
v.SetDefault("keycloak-username", "")
Expand Down Expand Up @@ -800,6 +812,8 @@ func config(logger log.Logger) *viper.Viper {

v.BindEnv("sentry-dsn", "CT_BRIDGE_SENTRY_DSN")

v.BindEnv("event-basic-auth-token", "CT_BRIDGE_EVENT_BASIC_AUTH")

// Load and log config.
v.SetConfigFile(v.GetString("config-file"))
var err = v.ReadInConfig()
Expand Down
6 changes: 5 additions & 1 deletion configs/keycloak_bridge.yml
Expand Up @@ -21,10 +21,14 @@ cors-allowed-headers:
cors-debug: true

# Security
## Audience for JWT token
audience-required: "backofficeid"

## Password used to protect /internal/event endpoint
event-basic-auth-token: "superpasswordverylongandstrong"


# Keycloak configs
keycloak: false
keycloak-api-uri: http://localhost:8080
keycloak-oidc-uri: http://localhost:8080
keycloak-username: admin
Expand Down
2 changes: 1 addition & 1 deletion pkg/management/component.go
Expand Up @@ -23,7 +23,7 @@ type KeycloakClient interface {
GetGroupsOfUser(accessToken string, realmName, userID string) ([]kc.GroupRepresentation, error)
UpdateUser(accessToken string, realmName, userID string, user kc.UserRepresentation) error
GetUsers(accessToken string, reqRealmName, targetRealmName string, paramKV ...string) ([]kc.UserRepresentation, error)
CreateUser(accessToken string, reqRealmName, targetRealmName string, user kc.UserRepresentation) (string, error)
CreateUser(accessToken string, realmName string, targetRealmName string, user kc.UserRepresentation) (string, error)
GetClientRoleMappings(accessToken string, realmName, userID, clientID string) ([]kc.RoleRepresentation, error)
AddClientRolesToUserRoleMapping(accessToken string, realmName, userID, clientID string, roles []kc.RoleRepresentation) error
GetRealmLevelRoleMappings(accessToken string, realmName, userID string) ([]kc.RoleRepresentation, error)
Expand Down
68 changes: 68 additions & 0 deletions pkg/middleware/authentication.go
Expand Up @@ -2,6 +2,7 @@ package middleware

import (
"context"
"encoding/base64"
"fmt"
"net/http"
"regexp"
Expand All @@ -11,6 +12,73 @@ import (
"github.com/go-kit/kit/log"
)

// MakeHTTPBasicAuthenticationMW retrieve the token from the HTTP header 'Basic' and
// check if the password value match the allowed one.
// If there is no such header, the request is not allowed.
// If the password is correct, the username is added into the context:
// - username: username extracted from the token
func MakeHTTPBasicAuthenticationMW(passwordToMatch string, logger log.Logger) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
var authorizationHeader = req.Header.Get("Authorization")

if authorizationHeader == "" {
logger.Log("Authorization Error", "Missing Authorization header")
httpErrorHandler(context.TODO(), http.StatusForbidden, fmt.Errorf("Missing Authorization header"), w)
return
}

var matched, _ = regexp.MatchString(`^[Bb]asic *`, authorizationHeader)

if !matched {
logger.Log("Authorization Error", "Missing basic token")
httpErrorHandler(context.TODO(), http.StatusForbidden, fmt.Errorf("Missing basic token"), w)
return
}

var splitToken = strings.Split(authorizationHeader, "Basic ")

if len(splitToken) < 2 {
splitToken = strings.Split(authorizationHeader, "basic ")
}

var accessToken = splitToken[1]

// Decode base 64
decodedToken, err := base64.StdEncoding.DecodeString(accessToken)

if err != nil {
logger.Log("Authorization Error", "Invalid base64 token")
httpErrorHandler(context.TODO(), http.StatusForbidden, fmt.Errorf("Invalid token"), w)
return
}

// Extract username & password values
var tokenSubparts = strings.Split(string(decodedToken), ":")

if len(tokenSubparts) != 2 {
logger.Log("Authorization Error", "Invalid token format (username:password)")
httpErrorHandler(context.TODO(), http.StatusForbidden, fmt.Errorf("Invalid token"), w)
return
}

var username = tokenSubparts[0]
var password = tokenSubparts[1]

// Check password match
if password != passwordToMatch {
logger.Log("Authorization Error", "Invalid password value")
httpErrorHandler(context.TODO(), http.StatusForbidden, fmt.Errorf("Invalid token"), w)
return
}

var ctx = context.WithValue(req.Context(), "username", username)

next.ServeHTTP(w, req.WithContext(ctx))
})
}
}

// KeycloakClient is the interface of the keycloak client.
type KeycloakClient interface {
VerifyToken(realmName string, accessToken string) error
Expand Down
89 changes: 89 additions & 0 deletions pkg/middleware/authentication_test.go
Expand Up @@ -18,6 +18,95 @@ import (
"github.com/stretchr/testify/assert"
)

func TestHTTPBasicAuthenticationMW(t *testing.T) {
var token = "dXNlcm5hbWU6cGFzc3dvcmQ="

var mockCtrl = gomock.NewController(t)
defer mockCtrl.Finish()
var mockLogger = mock.NewLogger(mockCtrl)

var m = MakeHTTPBasicAuthenticationMW("password", mockLogger)(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))

// HTTP request.
var req = httptest.NewRequest("POST", "http://cloudtrust.io/event/receiver", bytes.NewReader([]byte{}))

// Missing authorization token.
{
var w = httptest.NewRecorder()
mockLogger.EXPECT().Log("Authorization Error", "Missing Authorization header").Return(nil).Times(1)
m.ServeHTTP(w, req)
var result = w.Result()
assert.Equal(t, 403, result.StatusCode)
}

req.Header.Set("Authorization", "Non basic format")

// Missing basic token.
{
var w = httptest.NewRecorder()
mockLogger.EXPECT().Log("Authorization Error", "Missing basic token").Return(nil).Times(1)
m.ServeHTTP(w, req)
var result = w.Result()
assert.Equal(t, 403, result.StatusCode)
}

req.Header.Set("Authorization", "Basic "+token)

// Valid authorization token.
{
var w = httptest.NewRecorder()
m.ServeHTTP(w, req)
var result = w.Result()
assert.Equal(t, 200, result.StatusCode)
}

req.Header.Set("Authorization", "basic "+token)

// Valid authorization token.
{
var w = httptest.NewRecorder()
m.ServeHTTP(w, req)
var result = w.Result()
assert.Equal(t, 200, result.StatusCode)
}

req.Header.Set("Authorization", "basic dXNlcm5hbWU6cGFzc3dvcmQx")

// Invalid authorization token.
{
var w = httptest.NewRecorder()
mockLogger.EXPECT().Log("Authorization Error", "Invalid password value").Return(nil).Times(1)
m.ServeHTTP(w, req)
var result = w.Result()
assert.Equal(t, 403, result.StatusCode)
}

req.Header.Set("Authorization", "basic "+token)

// Invalid token format
{
var w = httptest.NewRecorder()
mockLogger.EXPECT().Log("Authorization Error", gomock.Any()).Return(nil).Times(1)
req = httptest.NewRequest("POST", "http://cloudtrust.io/management/test", bytes.NewReader([]byte{}))
req.Header.Set("Authorization", "Basic 123456ABCDEF")
m.ServeHTTP(w, req)
var result = w.Result()
assert.Equal(t, 403, result.StatusCode)
}

// Invalid token format
{
var w = httptest.NewRecorder()
mockLogger.EXPECT().Log("Authorization Error", gomock.Any()).Return(nil).Times(1)
req = httptest.NewRequest("POST", "http://cloudtrust.io/management/test", bytes.NewReader([]byte{}))
req.Header.Set("Authorization", "Basic dXNlcm5hbWU=")
m.ServeHTTP(w, req)
var result = w.Result()
assert.Equal(t, 403, result.StatusCode)
}

}

func TestHTTPOIDCTokenValidationMW(t *testing.T) {
var token = "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJJZTVzcXBLdTNwb1g5d1U3YTBhamxnUFlGRHFTTUF5M2l6NEZpelp4d2dnIn0.eyJqdGkiOiI4MDY4MjZkNy0xZjM4LTQxZjgtYTk5Ni1iYTYzYWI0YTY3MGIiLCJleHAiOjE1NTY2NjY3NzAsIm5iZiI6MCwiaWF0IjoxNTU2NjMwNzcwLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjgwODAvYXV0aC9yZWFsbXMvbWFzdGVyIiwiYXVkIjoidGVzdC1yZWFsbSIsInN1YiI6IjczOTNhYjFhLTViMDQtNDNmNS04MDQ5LThhOTQ5MjMyZWQwYSIsInR5cCI6IkJlYXJlciIsImF6cCI6ImFkbWluLWNsaSIsImF1dGhfdGltZSI6MCwic2Vzc2lvbl9zdGF0ZSI6IjFlMmI1Mzk5LTgyNDItNDA1OS05Y2M1LWE5MzI0NDVlY2JkMSIsImFjciI6IjEiLCJyZXNvdXJjZV9hY2Nlc3MiOnsidGVzdC1yZWFsbSI6eyJyb2xlcyI6WyJ2aWV3LXJlYWxtIiwidmlldy1pZGVudGl0eS1wcm92aWRlcnMiLCJtYW5hZ2UtaWRlbnRpdHktcHJvdmlkZXJzIiwiaW1wZXJzb25hdGlvbiIsImNyZWF0ZS1jbGllbnQiLCJtYW5hZ2UtdXNlcnMiLCJxdWVyeS1yZWFsbXMiLCJ2aWV3LWF1dGhvcml6YXRpb24iLCJxdWVyeS1jbGllbnRzIiwicXVlcnktdXNlcnMiLCJtYW5hZ2UtZXZlbnRzIiwibWFuYWdlLXJlYWxtIiwidmlldy1ldmVudHMiLCJ2aWV3LXVzZXJzIiwidmlldy1jbGllbnRzIiwibWFuYWdlLWF1dGhvcml6YXRpb24iLCJtYW5hZ2UtY2xpZW50cyIsInF1ZXJ5LWdyb3VwcyJdfX0sInNjb3BlIjoib3BlbmlkIHByb2ZpbGUgZ3JvdXBzIGVtYWlsIiwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJncm91cHMiOlsiL3RvZV9hZG1pbmlzdHJhdG9yIl0sInByZWZlcnJlZF91c2VybmFtZSI6ImFkbWluIiwiZW1haWwiOiJ0b3RvQHRvdG8uY29tIn0.QXUTPciZYYv8k688D27sOz5thyQH1OWwp-rqTnCQYoAbqXPVgSZxLIepk8JvS9drBl7jOH-M_w2tXMOjV-7kY7p57_9VyWaI42VgBVmJVXSWwMwPtWAwnpKqMh1wrrm_zYJRmZ43o1r6Rp_kELnfgwocFSLc3DTDVEoMuYE45kJg9JwPc2K7DYi6Om5qOm9ez-x8GpyGVy3xJiOa-Qr9oJpKCx02sRVEBIc0AE0pfpxfbBhJU06L4uVnwQ1JxquLKLU77bjPEkAKOnTeG-6D9OtH_K42KujZyhj7FytXAXv9CmISi9aIe7BVANFSu7TyOBjelZHVpI5dOKRc-E2L9w"

Expand Down
10 changes: 0 additions & 10 deletions pkg/middleware/correlation.go
Expand Up @@ -6,7 +6,6 @@ import (

gen "github.com/cloudtrust/keycloak-bridge/internal/idgenerator"
"github.com/go-kit/kit/log"
grpc_transport "github.com/go-kit/kit/transport/grpc"
opentracing "github.com/opentracing/opentracing-go"
)

Expand All @@ -26,12 +25,3 @@ func MakeHTTPCorrelationIDMW(idGenerator gen.IDGenerator, tracer opentracing.Tra
})
}
}

type correlationIDMW struct {
idGenerator gen.IDGenerator
tracer opentracing.Tracer
logger log.Logger
componentName string
componentID string
next grpc_transport.Handler
}
1 change: 0 additions & 1 deletion pkg/middleware/middleware_test.go
Expand Up @@ -5,7 +5,6 @@ package middleware
//go:generate mockgen -destination=./mock/tracing.go -package=mock -mock_names=Tracer=Tracer,Span=Span,SpanContext=SpanContext github.com/opentracing/opentracing-go Tracer,Span,SpanContext
//go:generate mockgen -destination=./mock/eventComponent.go -package=mock -mock_names=MuxComponent=MuxComponent,Component=EventComponent,AdminComponent=AdminEventComponent github.com/cloudtrust/keycloak-bridge/pkg/event MuxComponent,Component,AdminComponent
//go:generate mockgen -destination=./mock/idGenerator.go -package=mock -mock_names=IDGenerator=IDGenerator github.com/cloudtrust/keycloak-bridge/internal/idgenerator IDGenerator
//go:generate mockgen -destination=./mock/grpc.go -package=mock -mock_names=Handler=Handler github.com/go-kit/kit/transport/grpc Handler

import (
"bytes"
Expand Down

0 comments on commit 4bff54a

Please sign in to comment.