Skip to content
This repository has been archived by the owner on Dec 7, 2020. It is now read-only.

Commit

Permalink
- cleaning and reducing the amount of files, perfer to use just handl…
Browse files Browse the repository at this point in the history
…ers and middleware (#53)

- change config option in json/yaml of clientid to client-id
- added additional unit test for the sessions and cookie methods
- added support for password for redis
- added the ability to proxy to a unix socket
- cleaned up the handlers and middleware into respective files, i dont like lots of files laying around
  • Loading branch information
gambol99 committed Apr 30, 2016
1 parent 61c6fe9 commit ab17cf6
Show file tree
Hide file tree
Showing 27 changed files with 1,338 additions and 1,146 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ bin/
release/
cover.html
cover.out
tests/db.bolt
test.sock
tests/redis.conf

*.iml
config.yml
Expand Down
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,18 @@


#### **1.0.3 (April 30th, 2016)**

FIXES:
* Fixes the cookie sessions expiraton

FEATURES:
* Adding a idle duration configuration option which controls the expiration of access token cookie and thus session.
If the session is not used within that period, the session is removed.
* The upstream endpoint has also be a unix socket

BREAKING CHANGES:
* Change the client id in json/yaml config file from clientid -> client-id

#### **1.0.2 (April 22th, 2016)**

FIXES:
Expand Down
11 changes: 7 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@

NAME=keycloak-proxy
AUTHOR=gambol99
HARDWARE=$(shell uname -m)
REGISTRY=docker.io
GOVERSION=1.6.0
SUDO=sudo
GIT_COMMIT=$(shell git log --pretty=format:'%h' -n 1)
ROOT_DIR=${PWD}
HARDWARE=$(shell uname -m)
GIT_SHA=$(shell git --no-pager describe --tags --always --dirty)
VERSION=$(shell awk '/version.*=/ { print $$3 }' doc.go | sed 's/"//g')
DEPS=$(shell go list -f '{{range .TestImports}}{{.}} {{end}}' ./...)
PACKAGES=$(shell go list ./...)
Expand All @@ -20,12 +20,15 @@ golang:
@echo "--> Go Version"
@go version

build:
version:
@sed -i "s/const gitSHA =.*/const gitSHA = \"${GIT_SHA}\"/" doc.go

build: version
@echo "--> Compiling the project"
mkdir -p bin
godep go build -o bin/${NAME}

static: golang deps
static: version golang deps
@echo "--> Compiling the static binary"
mkdir -p bin
CGO_ENABLED=0 GOOS=linux godep go build -a -tags netgo -ldflags '-w' -o bin/${NAME}
Expand Down
62 changes: 36 additions & 26 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,13 @@ import (
// newDefaultConfig returns a initialized config
func newDefaultConfig() *Config {
return &Config{
Listen: "127.0.0.1:3000",
RedirectionURL: "http://127.0.0.1:3000",
Upstream: "http://127.0.0.1:8081",
TagData: make(map[string]string, 0),
ClaimsMatch: make(map[string]string, 0),
Header: make(map[string]string, 0),
CORS: &CORS{},
Listen: "127.0.0.1:3000",
RedirectionURL: "http://127.0.0.1:3000",
Upstream: "http://127.0.0.1:8081",
TagData: make(map[string]string, 0),
ClaimsMatch: make(map[string]string, 0),
Header: make(map[string]string, 0),
CrossOrigin: CORS{},
SkipUpstreamTLSVerify: true,
}
}
Expand Down Expand Up @@ -155,12 +155,18 @@ func readOptions(cx *cli.Context, config *Config) (err error) {
if cx.IsSet("upstream-keepalives") {
config.Keepalives = cx.Bool("upstream-keepalives")
}
if cx.IsSet("idle-duration") {
config.IdleDuration = cx.Duration("idle-duration")
}
if cx.IsSet("skip-token-verification") {
config.SkipTokenVerification = cx.Bool("skip-token-verification")
}
if cx.IsSet("skip-upstream-tls-verify") {
config.SkipUpstreamTLSVerify = cx.Bool("skip-upstream-tls-verify")
}
if cx.IsSet("enable-refresh-tokens") {
config.EnableRefreshTokens = cx.Bool("enable-refresh-tokens")
}
if cx.IsSet("encryption-key") {
config.EncryptionKey = cx.String("encryption-key")
}
Expand Down Expand Up @@ -210,22 +216,22 @@ func readOptions(cx *cli.Context, config *Config) (err error) {
config.Hostnames = cx.StringSlice("hostname")
}
if cx.IsSet("cors-origins") {
config.CORS.Origins = cx.StringSlice("cors-origins")
config.CrossOrigin.Origins = cx.StringSlice("cors-origins")
}
if cx.IsSet("cors-methods") {
config.CORS.Methods = cx.StringSlice("cors-methods")
config.CrossOrigin.Methods = cx.StringSlice("cors-methods")
}
if cx.IsSet("cors-headers") {
config.CORS.Headers = cx.StringSlice("cors-headers")
config.CrossOrigin.Headers = cx.StringSlice("cors-headers")
}
if cx.IsSet("cors-exposed-headers") {
config.CORS.ExposedHeaders = cx.StringSlice("cors-exposed-headers")
config.CrossOrigin.ExposedHeaders = cx.StringSlice("cors-exposed-headers")
}
if cx.IsSet("cors-max-age") {
config.CORS.MaxAge = cx.Duration("cors-max-age")
config.CrossOrigin.MaxAge = cx.Duration("cors-max-age")
}
if cx.IsSet("cors-credentials") {
config.CORS.Credentials = cx.BoolT("cors-credentials")
config.CrossOrigin.Credentials = cx.BoolT("cors-credentials")
}
if cx.IsSet("tag") {
config.TagData, err = decodeKeyPairs(cx.StringSlice("tag"))
Expand Down Expand Up @@ -302,35 +308,43 @@ func getOptions() []cli.Flag {
Name: "discovery-url",
Usage: "the discovery url to retrieve the openid configuration",
},
cli.DurationFlag{
Name: "idle-duration",
Usage: "the expiration of the access token cookie, if not used within this time its removed",
},
cli.StringFlag{
Name: "upstream-url",
Usage: "the url for the upstream endpoint you wish to proxy to",
Value: defaults.Upstream,
},
cli.StringFlag{
Name: "revocation-url",
Usage: "the url for the revocation endpoint to revoke refresh token, not all providers support the revocation_endpoint",
Usage: "the url for the revocation endpoint to revoke refresh token",
Value: "/oauth2/revoke",
},
cli.BoolTFlag{
Name: "upstream-keepalives",
Usage: "enables or disables the keepalive connections for upstream endpoint (defaults true)",
Usage: "enables or disables the keepalive connections for upstream endpoint",
},
cli.BoolFlag{
Name: "enable-refresh-tokens",
Usage: "enables the handling of the refresh tokens",
},
cli.StringFlag{
Name: "encryption-key",
Usage: "the encryption key used to encrpytion the session state",
},
cli.StringFlag{
Name: "store-url",
Usage: "the store url to use for storing the refresh tokens, i.e. redis://127.0.0.1:6379, file:///etc/tokens.file",
Usage: "url for the storage subsystem, e.g redis://127.0.0.1:6379, file:///etc/tokens.file",
},
cli.BoolFlag{
Name: "no-redirects",
Usage: "do not have back redirects when no authentication is present, simple reply with 401 code",
Usage: "do not have back redirects when no authentication is present, 401 them",
},
cli.StringFlag{
Name: "redirection-url",
Usage: fmt.Sprintf("the redirection url, namely the site url, note: %s will be added to it", oauthURL),
Usage: fmt.Sprintf("redirection url for the oauth callback url (%s is added)", oauthURL),
},
cli.StringSliceFlag{
Name: "hostname",
Expand Down Expand Up @@ -358,7 +372,7 @@ func getOptions() []cli.Flag {
},
cli.StringSliceFlag{
Name: "claim",
Usage: "a series of key pair values which must match the claims in the token present e.g. aud=myapp, iss=http://example.com etcd",
Usage: "keypair values for matching access token claims e.g. aud=myapp, iss=http://example.*",
},
cli.StringSliceFlag{
Name: "resource",
Expand All @@ -374,11 +388,11 @@ func getOptions() []cli.Flag {
},
cli.StringSliceFlag{
Name: "tag",
Usage: "a keypair tag which is passed to the templates when render, i.e. title='My Page',site='my name' etc",
Usage: "keypair's passed to the templates at render,e.g title='My Page'",
},
cli.StringSliceFlag{
Name: "cors-origins",
Usage: "a set of origins to add to the CORS access control (Access-Control-Allow-Origin)",
Usage: "list of origins to add to the CORE origins control (Access-Control-Allow-Origin)",
},
cli.StringSliceFlag{
Name: "cors-methods",
Expand Down Expand Up @@ -406,11 +420,7 @@ func getOptions() []cli.Flag {
},
cli.BoolFlag{
Name: "skip-token-verification",
Usage: "testing purposes ONLY, the option allows you to bypass the token verification, expiration and roles are still enforced",
},
cli.BoolFlag{
Name: "proxy-protocol",
Usage: "switches on proxy protocol support on the listen (not supported yet)",
Usage: "TESTING ONLY; bypass's token verification, expiration and roles enforced",
},
cli.BoolFlag{
Name: "offline-session",
Expand Down
7 changes: 4 additions & 3 deletions config_sample.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@
# is the url for retrieve the openid configuration - normally the <server>/auth/realm/<realm_name>
discovery-url: https://keycloak.example.com/auth/realms/commons
# the client id for the 'client' application
clientid: <CLIENT_ID>
client-id: <CLIENT_ID>
# the secret associated to the 'client' application
client-secret: <CLIENT_SECRET>
# the interface definition you wish the proxy to listen, all interfaces is specified as ':<port>'
listen: 127.0.0.1:3000
# whether to request offline access and use a refresh token
enable-refresh-tokens: true
# the max amount of time a session can stay alive without being used
idle-duration: 24h
# log all incoming requests
log-requests: true
# log in json format
Expand All @@ -31,8 +33,7 @@ upstream: http://127.0.0.1:80
# upstream-keepalives specified wheather you want keepalive on the upstream endpoint
upstream-keepalives: true
# additional scopes to add to add to the default (openid+email+profile)
scopes:
- vpn-user
scopes: []
# enables a more extra secuirty features
enable-security-filter: true
# a map of claims that MUST exist in the token presented and the value is it MUST match
Expand Down
4 changes: 2 additions & 2 deletions config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,14 @@ func TestReadConfiguration(t *testing.T) {
{
Content: `
discovery_url: https://keyclock.domain.com/
clientid: <client_id>
client-id: <client_id>
secret: <secret>
`,
},
{
Content: `
discovery_url: https://keyclock.domain.com
clientid: <client_id>
client-id: <client_id>
secret: <secret>
upstream: http://127.0.0.1:8080
redirection_url: http://127.0.0.1:3000
Expand Down
29 changes: 14 additions & 15 deletions cookies.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,17 +27,16 @@ import (
//
// dropCookie drops a cookie into the response
//
func dropCookie(cx *gin.Context, name, value string, expires time.Time) {
func dropCookie(cx *gin.Context, name, value string, duration time.Duration) {
cookie := &http.Cookie{
Name: name,
Domain: strings.Split(cx.Request.Host, ":")[0],
Path: "/",
HttpOnly: true,
Secure: true,
Value: value,
Name: name,
Domain: strings.Split(cx.Request.Host, ":")[0],
Path: "/",
Secure: true,
Value: value,
}
if !expires.IsZero() {
cookie.Expires = expires
if duration != 0 {
cookie.Expires = time.Now().Add(duration)
}

http.SetCookie(cx.Writer, cookie)
Expand All @@ -46,15 +45,15 @@ func dropCookie(cx *gin.Context, name, value string, expires time.Time) {
//
// dropAccessTokenCookie drops a access token cookie into the response
//
func dropAccessTokenCookie(cx *gin.Context, token jose.JWT) {
dropCookie(cx, cookieAccessToken, token.Encode(), time.Time{})
func dropAccessTokenCookie(cx *gin.Context, token jose.JWT, duration time.Duration) {
dropCookie(cx, cookieAccessToken, token.Encode(), duration)
}

//
// dropRefreshTokenCookie drops a refresh token cookie into the response
//
func dropRefreshTokenCookie(cx *gin.Context, token string, expires time.Time) {
dropCookie(cx, cookieRefreshToken, token, expires)
func dropRefreshTokenCookie(cx *gin.Context, token string, duration time.Duration) {
dropCookie(cx, cookieRefreshToken, token, duration)
}

//
Expand All @@ -69,12 +68,12 @@ func clearAllCookies(cx *gin.Context) {
// clearRefreshSessionCookie clears the session cookie
//
func clearRefreshTokenCookie(cx *gin.Context) {
dropCookie(cx, cookieRefreshToken, "", time.Now().Add(-1*time.Hour))
dropCookie(cx, cookieRefreshToken, "", time.Duration(-10*time.Hour))
}

//
// clearAccessTokenCookie clears the session cookie
//
func clearAccessTokenCookie(cx *gin.Context) {
dropCookie(cx, cookieAccessToken, "", time.Now().Add(-1*time.Hour))
dropCookie(cx, cookieAccessToken, "", time.Duration(-10*time.Hour))
}
45 changes: 45 additions & 0 deletions cookies_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,48 @@ limitations under the License.
*/

package main

import (
"testing"

"github.com/stretchr/testify/assert"
)

func TestDropCookie(t *testing.T) {
context := newFakeGinContext("GET", "/admin")
dropCookie(context, "test-cookie", "test-value", 0)

assert.Equal(t, context.Writer.Header().Get("Set-Cookie"),
"test-cookie=test-value; Path=/; Domain=127.0.0.1; Secure",
"we have not set the cookie, headers: %v", context.Writer.Header())

context = newFakeGinContext("GET", "/admin")
dropCookie(context, "test-cookie", "test-value", 0)
assert.NotEqual(t, context.Writer.Header().Get("Set-Cookie"),
"test-cookie=test-value; Path=/; Domain=127.0.0.2; HttpOnly; Secure",
"we have not set the cookie, headers: %v", context.Writer.Header())
}

func TestClearAccessTokenCookie(t *testing.T) {
context := newFakeGinContext("GET", "/admin")
clearAccessTokenCookie(context)
assert.Contains(t, context.Writer.Header().Get("Set-Cookie"),
"kc-access=; Path=/; Domain=127.0.0.1; Expires=",
"we have not cleared the, headers: %v", context.Writer.Header())
}

func TestClearRefreshAccessTokenCookie(t *testing.T) {
context := newFakeGinContext("GET", "/admin")
clearRefreshTokenCookie(context)
assert.Contains(t, context.Writer.Header().Get("Set-Cookie"),
"kc-state=; Path=/; Domain=127.0.0.1; Expires=",
"we have not cleared the, headers: %v", context.Writer.Header())
}

func TestClearAllCookies(t *testing.T) {
context := newFakeGinContext("GET", "/admin")
clearAllCookies(context)
assert.Contains(t, context.Writer.Header().Get("Set-Cookie"),
"kc-access=; Path=/; Domain=127.0.0.1; Expires=",
"we have not cleared the, headers: %v", context.Writer.Header())
}
Loading

0 comments on commit ab17cf6

Please sign in to comment.