Skip to content

Commit

Permalink
Introduces telemetry module and adds documentation
Browse files Browse the repository at this point in the history
Closes #27
Closes #34
  • Loading branch information
arekkas committed Nov 26, 2017
1 parent 31fa680 commit 572ea7c
Show file tree
Hide file tree
Showing 8 changed files with 159 additions and 89 deletions.
23 changes: 21 additions & 2 deletions cmd/helper_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,30 @@ import (
"strings"
"time"

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

func getHydraSDK() hydra.SDK {
sdk, err := hydra.NewSDK(&hydra.Configuration{
ClientID: viper.GetString("HYDRA_CLIENT_ID"),
ClientSecret: viper.GetString("HYDRA_CLIENT_SECRET"),
EndpointURL: viper.GetString("HYDRA_URL"),
Scopes: []string{"hydra.warden", "hydra.keys.*"},
})

if err != nil {
logger.WithError(err).Fatalln("Unable to connect to Hydra SDK")
return nil
}
return sdk
}

func refreshRules(c *proxyConfig, m *rule.CachedMatcher, fails int) {
duration, _ := time.ParseDuration(c.refreshDelay)
duration, _ := time.ParseDuration(viper.GetString("RULES_REFRESH_INTERVAL"))
if duration == 0 {
duration = time.Second * 30
}
Expand All @@ -34,7 +50,10 @@ func refreshRules(c *proxyConfig, m *rule.CachedMatcher, fails int) {
}

func refreshKeys(k rsakey.Manager, fails int) {
duration := time.Minute * 5
duration, _ := time.ParseDuration(viper.GetString("JWK_REFRESH_INTERVAL"))
if duration == 0 {
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")
Expand Down
1 change: 1 addition & 0 deletions cmd/root_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
func TestCommandLineInterface(t *testing.T) {
var osArgs = make([]string, len(os.Args))
os.Setenv("DATABASE_URL", "memory")
os.Setenv("ISSUER_URL", "memory")
os.Setenv("HYDRA_URL", "http://does-not-exist.com/")
os.Setenv("HYDRA_CLIENT_ID", "does-not-exist")
os.Setenv("HYDRA_CLIENT_SECRET", "does-not-exist")
Expand Down
34 changes: 7 additions & 27 deletions cmd/serve_all.go
Original file line number Diff line number Diff line change
@@ -1,46 +1,26 @@
package cmd

import (
"fmt"

"github.com/ory/hydra/sdk/go/hydra"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)

// allCmd represents the all command
var allCmd = &cobra.Command{
Use: "all",
Short: "A brief description of your command",
Short: "Runs both the proxy and management command",
Long: `For documentation on the available configuration options please run:
* hydra help serve management
* hydra help serve proxy`,
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")
}

pc := &proxyConfig{
hydra: &hydra.Configuration{
ClientID: viper.GetString("HYDRA_CLIENT_ID"),
ClientSecret: viper.GetString("HYDRA_CLIENT_SECRET"),
EndpointURL: viper.GetString("HYDRA_URL"),
Scopes: []string{"hydra.warden", "hydra.keys.*"},
},
rules: rules, backendURL: viper.GetString("BACKEND_URL"),
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", "hydra.keys.*"},
},
rules: rules,
address: fmt.Sprintf("%s:%s", viper.GetString("MANAGEMENT_HOST"), viper.GetString("MANAGEMENT_PORT")),
}
pc := &proxyConfig{rules: rules}
mc := &managementConfig{rules: rules}

go runManagement(mc)
runProxy(pc)
Expand Down
25 changes: 4 additions & 21 deletions cmd/serve_management.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ 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"
Expand All @@ -18,17 +17,11 @@ import (
)

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

func runManagement(c *managementConfig) {
sdk, err := hydra.NewSDK(c.hydra)
if err != nil {
logger.WithError(err).Fatalln("Unable to connect to Hydra SDK")
return
}
sdk := getHydraSDK()

keyManager := &rsakey.HydraManager{
SDK: sdk,
Expand All @@ -47,7 +40,7 @@ func runManagement(c *managementConfig) {

go refreshKeys(keyManager, 0)

addr := c.address
addr := fmt.Sprintf("%s:%s", viper.GetString("MANAGEMENT_HOST"), viper.GetString("MANAGEMENT_PORT"))
server := graceful.WithDefaults(&http.Server{
Addr: addr,
Handler: router,
Expand Down Expand Up @@ -89,17 +82,7 @@ HTTP CONTROLS
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", "hydra.keys.*"},
},
rules: rules,
address: fmt.Sprintf("%s:%s", viper.GetString("MANAGEMENT_HOST"), viper.GetString("MANAGEMENT_PORT")),
}

config := &managementConfig{rules: rules}
runManagement(config)
},
}
Expand Down
86 changes: 49 additions & 37 deletions cmd/serve_proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,27 +12,21 @@ import (

"github.com/meatballhat/negroni-logrus"
"github.com/ory/graceful"
"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/ory/oathkeeper/telemetry"
"github.com/pborman/uuid"
"github.com/rs/cors"
"github.com/segmentio/analytics-go"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/urfave/negroni"
)

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

// proxyCmd represents the proxy command
Expand Down Expand Up @@ -66,6 +60,9 @@ REQUIRED CONTROLS
- JWT_SHARED_SECRET: The shared secret to be used to encrypt the Authorization Bearer JSON Web Token. Use this
secret to validate that the Bearer Token was indeed issued by this ORY Oathkeeper instance.
- ISSUER_URL: The public URL where this proxy is listening on.
Example: ISSUER_URL=https://my-api.com
HTTP(S) CONTROLS
==============
Expand All @@ -85,10 +82,16 @@ HTTP(S) CONTROLS
OTHER CONTROLS
==============
- RULES_REFRESH_INTERVAL: 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 refresh interval.
Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".
Default: RULES_REFRESH_INTERVAL=5s
- JWK_REFRESH_INTERVAL: ORY Oathkeeper stores JSON Web Keys for ID Token signing in memory. This value sets the refresh interval.
Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".
Default: JWK_REFRESH_INTERVAL=5m
- HYDRA_JWK_SET_ID: The JSON Web Key set identifier that will be used to create, store, and retrieve the JSON Web Key from ORY Hydra.
Default: HYDRA_JWK_SET_ID=oathkeeper:id-token
` + corsMessage,
Run: func(cmd *cobra.Command, args []string) {
rules, err := newRuleManager(viper.GetString("DATABASE_URL"))
Expand All @@ -97,35 +100,26 @@ OTHER CONTROLS
}

config := &proxyConfig{
hydra: &hydra.Configuration{
ClientID: viper.GetString("HYDRA_CLIENT_ID"),
ClientSecret: viper.GetString("HYDRA_CLIENT_SECRET"),
EndpointURL: viper.GetString("HYDRA_URL"),
Scopes: []string{"hydra.warden", "hydra.keys.*"},
},
rules: rules, backendURL: viper.GetString("BACKEND_URL"),
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"),
rules: rules,
}

runProxy(config)
},
}

func runProxy(c *proxyConfig) {
sdk, err := hydra.NewSDK(c.hydra)
if err != nil {
logger.WithError(err).Fatalln("Unable to connect to Hydra SDK")
return
}
backend, err := url.Parse(c.backendURL)
sdk := getHydraSDK()

backend, err := url.Parse(viper.GetString("BACKEND_URL"))
if err != nil {
logger.WithError(err).Fatalln("Unable to parse backend URL")
}

issuer := viper.GetString("ISSUER_URL")
if issuer == "" {
logger.Fatalln("Please set the issuer URL using the environment variable ISSUER_URL")
}

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

if err := matcher.Refresh(); err != nil {
Expand All @@ -137,10 +131,24 @@ func runProxy(c *proxyConfig) {
Set: viper.GetString("HYDRA_JWK_SET_ID"),
}

segmentMiddleware := new(telemetry.Middleware)
segment := telemetry.Manager{
Segment: analytics.New("MSx9A6YQ1qodnkzEFOv22cxOmOCJXMFa"),
Middleware: segmentMiddleware,
ID: issuer,
BuildVersion: Version,
BuildTime: BuildTime,
BuildHash: GitHash,
Logger: logger,
InstanceID: uuid.New(),
}

go segment.Identify()
go segment.Submit()
go refreshRules(c, matcher, 0)
go refreshKeys(keyManager, 0)

eval := evaluator.NewWardenEvaluator(logger, matcher, sdk)
eval := evaluator.NewWardenEvaluator(logger, matcher, sdk, issuer)
d := director.NewDirector(backend, eval, logger, keyManager)
proxy := &httputil.ReverseProxy{
Director: d.Director,
Expand All @@ -149,32 +157,36 @@ func runProxy(c *proxyConfig) {

n := negroni.New()
n.Use(negronilogrus.NewMiddlewareFromLogger(logger, "oahtkeeper-proxy"))
n.Use(segmentMiddleware)
n.UseHandler(proxy)

ch := cors.New(c.cors).Handler(n)
ch := cors.New(parseCorsOptions("")).Handler(n)

var cert tls.Certificate
if c.tlsCert != "" && c.tlsKey != "" {
if tlsCert, err := base64.StdEncoding.DecodeString(c.tlsCert); err != nil {
tlsCert := viper.GetString("HTTP_TLS_CERT")
tlsKey := viper.GetString("HTTP_TLS_KEY")
if tlsCert != "" && tlsKey != "" {
if tlsCert, err := base64.StdEncoding.DecodeString(tlsCert); err != nil {
logger.WithError(err).Fatalln("Unable to base64 decode the TLS Certificate")
} else if tlsKey, err := base64.StdEncoding.DecodeString(c.tlsKey); err != nil {
} else if tlsKey, err := base64.StdEncoding.DecodeString(tlsKey); err != nil {
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")
}
}

addr := fmt.Sprintf("%s:%s", viper.GetString("PROXY_HOST"), viper.GetString("PROXY_PORT"))
server := graceful.WithDefaults(&http.Server{
Addr: c.address,
Addr: addr,
Handler: ch,
TLSConfig: &tls.Config{
Certificates: []tls.Certificate{cert},
},
})

logger.Printf("Listening on %s.\n", c.address)
logger.Printf("Listening on %s.\n", addr)
if err := graceful.Graceful(func() error {
if c.tlsCert != "" && c.tlsKey != "" {
if tlsCert != "" && tlsKey != "" {
return server.ListenAndServeTLS("", "")
}
return server.ListenAndServe()
Expand Down
4 changes: 2 additions & 2 deletions director/director_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ func TestProxy(t *testing.T) {

matcher := &rule.CachedMatcher{Rules: tc.rules}
sdk := tc.mock(ctrl)
d.Evaluator = evaluator.NewWardenEvaluator(nil, matcher, sdk)
d.Evaluator = evaluator.NewWardenEvaluator(nil, matcher, sdk, "")

req, err := http.NewRequest("GET", tc.url, nil)
require.NoError(t, err)
Expand Down Expand Up @@ -186,7 +186,7 @@ func BenchmarkDirector(b *testing.B) {
{MatchesMethods: []string{"GET"}, MatchesURLCompiled: panicCompileRegex(proxy.URL + "/<[0-9]+>"), Mode: rule.AnonymousMode},
{MatchesMethods: []string{"GET"}, MatchesURLCompiled: panicCompileRegex(proxy.URL + "/other/<.+>"), Mode: rule.AnonymousMode},
}}
d.Evaluator = evaluator.NewWardenEvaluator(logger, matcher, nil)
d.Evaluator = evaluator.NewWardenEvaluator(logger, matcher, nil, "")

req, _ := http.NewRequest("GET", proxy.URL+"/users", nil)

Expand Down
Loading

0 comments on commit 572ea7c

Please sign in to comment.