Skip to content

Commit

Permalink
Replace shared secret with RSA key from Hydra for ID token signing
Browse files Browse the repository at this point in the history
  • Loading branch information
arekkas authored and arekkas committed Nov 18, 2017
1 parent 87f0700 commit e7ed8ca
Show file tree
Hide file tree
Showing 19 changed files with 452 additions and 71 deletions.
8 changes: 8 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,14 @@ jobs:
- image: circleci/golang:1.9
environment:
- PG_URL=postgres://test:test@localhost:5432/oathkeeper?sslmode=disable
- TEST_HYDRA_URL=http://localhost:4444
- TEST_HYDRA_CLIENT_ID=root
- TEST_HYDRA_CLIENT_SECRET=secret
- FORCE_ROOT_CLIENT_CREDENTIALS=root:secret
- image: oryd/hydra:v0.10.0-alpha.18
environment:
- DATABASE_URL=memory
command: "host --dangerous-force-http"
- image: postgres:9.5
environment:
- POSTGRES_USER=test
Expand Down
10 changes: 8 additions & 2 deletions Gopkg.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion cmd/helper_response.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ func checkResponse(response *swagger.APIResponse, err error, expectedStatusCode
must(err, "Could not validate token: %s", err)

if response.StatusCode != expectedStatusCode {
fmt.Printf("Command failed because status code %d was expeceted but code %d was received.", expectedStatusCode, response.StatusCode)
fmt.Printf("Command failed because status code %d was expeceted but code %d was received", expectedStatusCode, response.StatusCode)
os.Exit(1)
return
}
Expand Down
31 changes: 26 additions & 5 deletions cmd/helper_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,30 +5,51 @@ import (
"strings"
"time"

"github.com/ory/oathkeeper/rsakey"
"github.com/ory/oathkeeper/rule"
"github.com/rs/cors"
"github.com/spf13/viper"
)

func refresh(c *proxyConfig, m *rule.CachedMatcher, fails int) {
func refreshRules(c *proxyConfig, m *rule.CachedMatcher, fails int) {
duration, _ := time.ParseDuration(c.refreshDelay)
if duration == 0 {
duration = time.Second * 30
}

if err := m.Refresh(); err != nil {
logger.WithError(err).WithField("retry", fails).Errorln("Unable to refresh rules.")
logger.WithError(err).WithField("retry", fails).Errorln("Unable to refresh rules")
if fails > 15 {
logger.WithError(err).WithField("retry", fails).Fatalf("Terminating after retry %d.\n", fails)
logger.WithError(err).WithField("retry", fails).Fatalf("Terminating after retry %d\n", fails)
}

refresh(c, m, fails+1)
time.Sleep(time.Second * time.Duration(fails+1))
refreshRules(c, m, fails+1)
return
}

time.Sleep(duration)

refresh(c, m, 0)
refreshRules(c, m, 0)
}

func refreshKeys(k rsakey.Manager, fails int) {
duration := time.Minute * 5

if err := k.Refresh(); err != nil {
logger.WithError(err).WithField("retry", fails).Errorln("Unable to refresh RSA keys for JWK signing")
if fails > 15 {
logger.WithError(err).WithField("retry", fails).Fatalf("Terminating after retry %d\n", fails)
}

time.Sleep(time.Second * time.Duration(fails+1))
refreshKeys(k, fails+1)
return
}

time.Sleep(duration)

refreshKeys(k, 0)
}

func parseCorsOptions(prefix string) cors.Options {
Expand Down
3 changes: 2 additions & 1 deletion cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,8 @@ func initConfig() {
viper.SetDefault("LOG_LEVEL", "info")
viper.SetDefault("PROXY_PORT", "4455")
viper.SetDefault("MANAGEMENT_PORT", "4456")
viper.SetDefault("REFRESH_DELAY", "5s")
viper.SetDefault("RULES_REFRESH_INTERVAL", "5s")
viper.SetDefault("HYDRA_JWK_SET_ID", "oathkeeper:id-token")

// If a config file is found, read it in.
if err := viper.ReadInConfig(); err == nil {
Expand Down
2 changes: 1 addition & 1 deletion cmd/root_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ func TestCommandLineInterface(t *testing.T) {
if c.wait != nil {
var count = 0
for c.wait() {
t.Logf("Port is not yet open, retrying attempt #%d...", count)
t.Logf("Port is not yet open, retrying attempt #%d..", count)
count++
if count > 5 {
t.FailNow()
Expand Down
15 changes: 10 additions & 5 deletions cmd/serve_all.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ var allCmd = &cobra.Command{
Run: func(cmd *cobra.Command, args []string) {
rules, err := newRuleManager(viper.GetString("DATABASE_URL"))
if err != nil {
logger.WithError(err).Fatalln("Unable to connect to rule backend.")
logger.WithError(err).Fatalln("Unable to connect to rule backend")
}

pc := &proxyConfig{
Expand All @@ -26,13 +26,18 @@ var allCmd = &cobra.Command{
Scopes: []string{"hydra.warden"},
},
rules: rules, backendURL: viper.GetString("BACKEND_URL"),
bearerTokenSecret: viper.GetString("JWT_SHARED_SECRET"),
cors: parseCorsOptions(""),
address: fmt.Sprintf("%s:%s", viper.GetString("PROXY_HOST"), viper.GetString("PROXY_PORT")),
refreshDelay: viper.GetString("REFRESH_DELAY"),
cors: parseCorsOptions(""),
address: fmt.Sprintf("%s:%s", viper.GetString("PROXY_HOST"), viper.GetString("PROXY_PORT")),
refreshDelay: viper.GetString("RULES_REFRESH_INTERVAL"),
}

mc := &managementConfig{
hydra: &hydra.Configuration{
ClientID: viper.GetString("HYDRA_CLIENT_ID"),
ClientSecret: viper.GetString("HYDRA_CLIENT_SECRET"),
EndpointURL: viper.GetString("HYDRA_URL"),
Scopes: []string{"hydra.warden"},
},
rules: rules,
address: fmt.Sprintf("%s:%s", viper.GetString("MANAGEMENT_HOST"), viper.GetString("MANAGEMENT_PORT")),
}
Expand Down
30 changes: 27 additions & 3 deletions cmd/serve_management.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,26 +9,44 @@ import (
"github.com/meatballhat/negroni-logrus"
"github.com/ory/graceful"
"github.com/ory/herodot"
"github.com/ory/hydra/sdk/go/hydra"
"github.com/ory/oathkeeper/rsakey"
"github.com/ory/oathkeeper/rule"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/urfave/negroni"
)

type managementConfig struct {
hydra *hydra.Configuration
rules rule.Manager
address string
}

func runManagement(c *managementConfig) {
handler := rule.Handler{H: herodot.NewJSONWriter(logger), M: c.rules}
sdk, err := hydra.NewSDK(c.hydra)
if err != nil {
logger.WithError(err).Fatalln("Unable to connect to Hydra SDK")
return
}

keyManager := &rsakey.HydraManager{
SDK: sdk,
Set: viper.GetString("HYDRA_JWK_SET_ID"),
}

rules := rule.Handler{H: herodot.NewJSONWriter(logger), M: c.rules}
keys := rsakey.Handler{H: herodot.NewJSONWriter(logger), M: keyManager}
router := httprouter.New()
handler.SetRoutes(router)
rules.SetRoutes(router)
keys.SetRoutes(router)

n := negroni.New()
n.Use(negronilogrus.NewMiddlewareFromLogger(logger, "oathkeeper-management"))
n.UseHandler(router)

go refreshKeys(keyManager, 0)

addr := c.address
server := graceful.WithDefaults(&http.Server{
Addr: addr,
Expand Down Expand Up @@ -68,10 +86,16 @@ HTTP CONTROLS
Run: func(cmd *cobra.Command, args []string) {
rules, err := newRuleManager(viper.GetString("DATABASE_URL"))
if err != nil {
logger.WithError(err).Fatalln("Unable to connect to rule backend.")
logger.WithError(err).Fatalln("Unable to connect to rule backend")
}

config := &managementConfig{
hydra: &hydra.Configuration{
ClientID: viper.GetString("HYDRA_CLIENT_ID"),
ClientSecret: viper.GetString("HYDRA_CLIENT_SECRET"),
EndpointURL: viper.GetString("HYDRA_URL"),
Scopes: []string{"hydra.warden"},
},
rules: rules,
address: fmt.Sprintf("%s:%s", viper.GetString("MANAGEMENT_HOST"), viper.GetString("MANAGEMENT_PORT")),
}
Expand Down
59 changes: 32 additions & 27 deletions cmd/serve_proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"github.com/ory/hydra/sdk/go/hydra"
"github.com/ory/oathkeeper/director"
"github.com/ory/oathkeeper/evaluator"
"github.com/ory/oathkeeper/rsakey"
"github.com/ory/oathkeeper/rule"
"github.com/rs/cors"
"github.com/spf13/cobra"
Expand All @@ -23,16 +24,15 @@ import (
)

type proxyConfig struct {
hydra *hydra.Configuration
backendURL string
databaseURL string
cors cors.Options
address string
refreshDelay string
rules rule.Manager
bearerTokenSecret string
tlsCert string
tlsKey string
hydra *hydra.Configuration
backendURL string
databaseURL string
cors cors.Options
address string
refreshDelay string
rules rule.Manager
tlsCert string
tlsKey string
}

// proxyCmd represents the proxy command
Expand Down Expand Up @@ -85,15 +85,15 @@ HTTP(S) CONTROLS
OTHER CONTROLS
==============
- REFRESH_DELAY: ORY Oathkeeper stores rules in memory for faster access. This value sets the database polling interval.
- RULES_REFRESH_INTERVAL: ORY Oathkeeper stores rules in memory for faster access. This value sets the database polling interval.
Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".
Default: REFRESH_DELY=5s
Default: RULES_REFRESH_INTERVAL=5s
` + corsMessage,
Run: func(cmd *cobra.Command, args []string) {
rules, err := newRuleManager(viper.GetString("DATABASE_URL"))
if err != nil {
logger.WithError(err).Fatalln("Unable to connect to rule backend.")
logger.WithError(err).Fatalln("Unable to connect to rule backend")
}

config := &proxyConfig{
Expand All @@ -104,12 +104,11 @@ OTHER CONTROLS
Scopes: []string{"hydra.warden"},
},
rules: rules, backendURL: viper.GetString("BACKEND_URL"),
bearerTokenSecret: viper.GetString("JWT_SHARED_SECRET"),
cors: parseCorsOptions(""),
address: fmt.Sprintf("%s:%s", viper.GetString("PROXY_HOST"), viper.GetString("PROXY_PORT")),
refreshDelay: viper.GetString("REFRESH_DELAY"),
tlsKey: viper.GetString("HTTP_TLS_KEY"),
tlsCert: viper.GetString("HTTP_TLS_CERT"),
cors: parseCorsOptions(""),
address: fmt.Sprintf("%s:%s", viper.GetString("PROXY_HOST"), viper.GetString("PROXY_PORT")),
refreshDelay: viper.GetString("RULES_REFRESH_INTERVAL"),
tlsKey: viper.GetString("HTTP_TLS_KEY"),
tlsCert: viper.GetString("HTTP_TLS_CERT"),
}

runProxy(config)
Expand All @@ -119,24 +118,30 @@ OTHER CONTROLS
func runProxy(c *proxyConfig) {
sdk, err := hydra.NewSDK(c.hydra)
if err != nil {
logger.WithError(err).Fatalln("Unable to connect to Hydra SDK.")
logger.WithError(err).Fatalln("Unable to connect to Hydra SDK")
return
}
backend, err := url.Parse(c.backendURL)
if err != nil {
logger.WithError(err).Fatalln("Unable to parse backend URL.")
logger.WithError(err).Fatalln("Unable to parse backend URL")
}

matcher := &rule.CachedMatcher{Manager: c.rules, Rules: []rule.Rule{}}

if err := matcher.Refresh(); err != nil {
logger.WithError(err).Fatalln("Unable to refresh rules.")
logger.WithError(err).Fatalln("Unable to refresh rules")
}

go refresh(c, matcher, 0)
keyManager := &rsakey.HydraManager{
SDK: sdk,
Set: viper.GetString("HYDRA_JWK_SET_ID"),
}

go refreshRules(c, matcher, 0)
go refreshKeys(keyManager, 0)

eval := evaluator.NewWardenEvaluator(logger, matcher, sdk)
d := director.NewDirector(backend, eval, logger, c.bearerTokenSecret)
d := director.NewDirector(backend, eval, logger, keyManager)
proxy := &httputil.ReverseProxy{
Director: d.Director,
Transport: d,
Expand All @@ -151,11 +156,11 @@ func runProxy(c *proxyConfig) {
var cert tls.Certificate
if c.tlsCert != "" && c.tlsKey != "" {
if tlsCert, err := base64.StdEncoding.DecodeString(c.tlsCert); err != nil {
logger.WithError(err).Fatalln("Unable to base64 decode the TLS Certificate.")
logger.WithError(err).Fatalln("Unable to base64 decode the TLS Certificate")
} else if tlsKey, err := base64.StdEncoding.DecodeString(c.tlsKey); err != nil {
logger.WithError(err).Fatalln("Unable to base64 decode the TLS Private Key.")
logger.WithError(err).Fatalln("Unable to base64 decode the TLS Private Key")
} else if cert, err = tls.X509KeyPair(tlsCert, tlsKey); err != nil {
logger.WithError(err).Fatalln("Unable to load X509 key pair.")
logger.WithError(err).Fatalln("Unable to load X509 key pair")
}
}

Expand Down
Loading

0 comments on commit e7ed8ca

Please sign in to comment.