Skip to content

Commit

Permalink
cmd: clean up env to struct mapping, add more controls
Browse files Browse the repository at this point in the history
* closes #184
* closes #183
* closes #182
  • Loading branch information
Aeneas Rekkas (arekkas) committed Jul 30, 2016
1 parent 242ca83 commit 0195703
Show file tree
Hide file tree
Showing 6 changed files with 175 additions and 160 deletions.
78 changes: 72 additions & 6 deletions cmd/host.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,32 +34,99 @@ const (
var hostCmd = &cobra.Command{
Use: "host",
Short: "Start the HTTP/2 host service",
Long: `Starts all HTTP/2 APIs and connects to a backend.
Long: `Starts all HTTP/2 APIs and connects to a database backend.
This command supports the following environment variables:
This command exposes a variety of controls via environment variables. You can
set environments using "export KEY=VALUE" (Linux/macOS) or "set KEY=VALUE" (Windows). On Linux,
you can also set environments by prepending key value pairs: "KEY=VALUE KEY2=VALUE2 hydra"
All possible controls are listed below. The host process additionally exposes a few flags, which are listed below
the controls section.
CORE CONTROLS
=============
- DATABASE_URL: A URL to a persistent backend. Hydra supports various backends:
- None: If DATABASE_URL is empty, all data will be lost when the command is killed.
- RethinkDB: If DATABASE_URL is a DSN starting with rethinkdb://, RethinkDB will be used as storage backend.
Example: DATABASE_URL=rethinkdb://user:password@host:123/database
Additionally, these controls are available when using RethinkDB:
- RETHINK_TLS_CERT_PATH: The path to the TLS certificate (pem encoded) used to connect to rethinkdb.
Example: RETHINK_TLS_CERT_PATH=~/rethink.pem
- RETHINK_TLS_CERT: A pem encoded TLS certificate passed as string. Can be used instead of RETHINK_TLS_CERT_PATH.
Example: RETHINK_TLS_CERT_PATH="-----BEGIN CERTIFICATE-----\nMIIDZTCCAk2gAwIBAgIEV5xOtDANBgkqhkiG9w0BAQ0FADA0MTIwMAYDVQQDDClP..."
- SYSTEM_SECRET: A secret that is at least 16 characters long. If none is provided, one will be generated. They key
is used to encrypt sensitive data using AES-GCM (256 bit) and validate HMAC signatures.
Example: SYSTEM_SECRET=jf89-jgklAS9gk3rkAF90dfsk
- FORCE_ROOT_CLIENT_CREDENTIALS: On first start up, Hydra generates a root client with random id and secret. Use
this environment variable in the form of "FORCE_ROOT_CLIENT_CREDENTIALS=id:secret" to set
the client id and secret yourself.
Example: FORCE_ROOT_CLIENT_CREDENTIALS=admin:kf0AKfm12fas3F-.f
- PORT: The port hydra should listen on.
Defaults to PORT=4444
- HOST: The port hydra should listen on.
Example: PORT=localhost
- BCRYPT_COST: Set the bcrypt hashing cost. This is a trade off between
security and performance. Range is 4 =< x =< 31.
Defaults to BCRYPT_COST=10
OAUTH2 CONTROLS
===============
- CONSENT_URL: The uri of the consent endpoint.
Example: CONSENT_URL=https://id.myapp.com/consent
- ISSUER: The issuer is used for identification in all OAuth2 tokens.
Defaults to ISSUER=hydra.localhost
- AUTH_CODE_LIFESPAN: Lifespan of OAuth2 authorize codes. Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".
Defaults to AUTH_CODE_LIFESPAN=10m
- ID_TOKEN_LIFESPAN: Lifespan of OpenID Connect ID Tokens. Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".
Defaults to AUTH_CODE_LIFESPAN=1h
- ACCESS_TOKEN_LIFESPAN: Lifespan of OAuth2 access tokens. Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".
Defaults to AUTH_CODE_LIFESPAN=1h
- CHALLENGE_TOKEN_LIFESPAN: Lifespan of OAuth2 consent tokens. Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".
Defaults to AUTH_CODE_LIFESPAN=10m
HTTPS CONTROLS
==============
- HTTPS_ALLOW_TERMINATION_FROM: Whitelist one or multiple CIDR address ranges and allow them to terminate TLS connections.
Be aware that the X-Forwarded-Proto header must be set and must never be modifiable by anyone but
your proxy / gateway / load balancer.
Example: HTTPS_ALLOW_TERMINATION_FROM=["127.0.0.1/16","192.168.178.0/16"]
- HTTPS_TLS_CERT_PATH: The path to the TLS certificate (pem encoded).
Example: HTTPS_TLS_CERT_PATH=~/cert.pem
- HTTPS_TLS_KEY_PATH: The path to the TLS private key (pem encoded).
Example: HTTPS_TLS_KEY_PATH=~/key.pem
- HTTPS_TLS_CERT: A pem encoded TLS certificate passed as string. Can be used instead of HTTPS_TLS_CERT_PATH.
Example: HTTPS_TLS_CERT="-----BEGIN CERTIFICATE-----\nMIIDZTCCAk2gAwIBAgIEV5xOtDANBgkqhkiG9w0BAQ0FADA0MTIwMAYDVQQDDClP..."
- HTTPS_TLS_KEY: A pem encoded TLS key passed as string. Can be used instead of HTTPS_TLS_KEY_PATH.
Example: HTTPS_TLS_KEY="-----BEGIN ENCRYPTED PRIVATE KEY-----\nMIIFDjBABgkqhkiG9w0BBQ0wMzAbBgkqhkiG9w0BBQwwDg..."
- RETHINK_TLS_CERT_PATH: The path to the TLS certificate (pem encoded) used to connect to rethinkdb.
- RETHINK_TLS_CERT: A pem encoded TLS certificate passed as string. Can be used instead of RETHINK_TLS_CERT_PATH.
DEBUG CONTROLS
==============
- HYDRA_PROFILING: Set "HYDRA_PROFILING=cpu" to enable cpu profiling and "HYDRA_PROFILING=memory" to enable memory profiling.
It is not possible to do both at the same time.
Example: HYDRA_PROFILING=cpu
`,
Run: runHostCmd,
}
Expand All @@ -76,7 +143,6 @@ func init() {
// Cobra supports local flags which will only run when this command
// is called directly, e.g.:
hostCmd.Flags().Bool("force-dangerous-http", false, "Disable HTTP/2 over TLS (HTTPS) and serve HTTP instead. Never use this in production.")
hostCmd.Flags().StringSlice("allow-tls-termination-from", []string{}, "Whitelist one or multiple CIDR ip address ranges and allow them to terminate TLS connections. This is common when using load balancers, proxies or other types of gateways. Be aware that the `X-Forwarded-Proto` header, indicating the original protocol, must be set. This header must never be set by anyone else except the proxy / load balancer / ...")
hostCmd.Flags().Bool("dangerous-auto-logon", false, "Stores the root credentials in ~/.hydra.yml. Do not use in production.")
hostCmd.Flags().String("https-tls-key-path", "", "Path to the key file for HTTP/2 over TLS (https). You can set HTTPS_TLS_KEY_PATH or HTTPS_TLS_KEY instead.")
hostCmd.Flags().String("https-tls-cert-path", "", "Path to the certificate file for HTTP/2 over TLS (https). You can set HTTPS_TLS_CERT_PATH or HTTPS_TLS_CERT instead.")
Expand Down Expand Up @@ -165,7 +231,7 @@ func getOrCreateTLSCertificate(cmd *cobra.Command) tls.Certificate {
ctx := c.Context()
keys, err := ctx.KeyManager.GetKey(TLSKeyName, "private")
if errors.Is(err, pkg.ErrNotFound) {
logrus.Warn("Key for TLS not found. Creating new one.")
logrus.Warn("No TLS Key / Certificate for HTTPS found. Generating self-signed certificate.")

keys, err = new(jwk.ECDSA256Generator).Generate("")
pkg.Must(err, "Could not generate key: %s", err)
Expand Down
62 changes: 32 additions & 30 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@ import (
"path/filepath"
"runtime"
"strings"
"sync"

"github.com/ory-am/hydra/cmd/cli"
"github.com/ory-am/hydra/config"
"github.com/spf13/cobra"
Expand All @@ -22,14 +20,13 @@ var c = new(config.Config)
// This represents the base command when called without any subcommands
var RootCmd = &cobra.Command{
Use: "hydra",
Short: "Hydra is a twelve factor OAuth2 and OpenID Connect provider",
Short: "Hydra is a cloud native high throughput OAuth2 and OpenID Connect provider",
// Uncomment the following line if your bare application
// has an action associated with it:
// Run: func(cmd *cobra.Command, args []string) { },
}

var cmdHandler = cli.NewHandler(c)
var mutex = &sync.RWMutex{}

// Execute adds all child commands to the root command sets flags appropriately.
// This is called by main.main(). It only needs to happen once to the rootCmd.
Expand Down Expand Up @@ -57,7 +54,6 @@ func init() {

// initConfig reads in config file and ENV variables if set.
func initConfig() {
mutex.Lock()
if cfgFile != "" {
// enable ability to specify config file via flag
viper.SetConfigFile(cfgFile)
Expand All @@ -73,40 +69,46 @@ func initConfig() {
viper.AddConfigPath("$HOME") // adding home directory as first search path
viper.AutomaticEnv() // read in environment variables that match

// If a config file is found, read it in.
if err := viper.ReadInConfig(); err != nil {
fmt.Printf(`Config file not found because "%s"`, err)
fmt.Println("")
}
viper.BindEnv("HOST")
viper.BindEnv("CLIENT_ID")
viper.BindEnv("CONSENT_URL")
viper.BindEnv("DATABASE_URL")
viper.BindEnv("SYSTEM_SECRET")
viper.BindEnv("CLIENT_SECRET")

if err := viper.Unmarshal(c); err != nil {
fatal(fmt.Sprintf("Could not read config because %s.", err))
}
viper.BindEnv("CLUSTER_URL")
viper.SetDefault("CLUSTER_URL", "https://localhost:4444")

if consentURL, ok := viper.Get("CONSENT_URL").(string); ok {
c.ConsentURL = consentURL
}
viper.BindEnv("PORT")
viper.SetDefault("PORT", 4444)

if clientID, ok := viper.Get("CLIENT_ID").(string); ok {
c.ClientID = clientID
}
viper.BindEnv("ISSUER")
viper.SetDefault("ISSUER", "hydra.localhost")

if systemSecret, ok := viper.Get("SYSTEM_SECRET").(string); ok {
c.SystemSecret = []byte(systemSecret)
}
viper.BindEnv("BCRYPT_COST")
viper.SetDefault("BCRYPT_COST", 10)

if clientSecret, ok := viper.Get("CLIENT_SECRET").(string); ok {
c.ClientSecret = clientSecret
}
viper.BindEnv("ACCESS_TOKEN_LIFESPAN")
viper.SetDefault("ACCESS_TOKEN_LIFESPAN", "1h")

viper.BindEnv("ID_TOKEN_LIFESPAN")
viper.SetDefault("ID_TOKEN_LIFESPAN", "1h")

if databaseURL, ok := viper.Get("DATABASE_URL").(string); ok {
c.DatabaseURL = databaseURL
viper.BindEnv("AUTH_CODE_LIFESPAN")
viper.SetDefault("AUTH_CODE_LIFESPAN", "10m")

viper.BindEnv("CHALLENGE_TOKEN_LIFESPAN")
viper.SetDefault("CHALLENGE_TOKEN_LIFESPAN", "10m")

// If a config file is found, read it in.
if err := viper.ReadInConfig(); err != nil {
fmt.Printf(`Config file not found because "%s"`, err)
fmt.Println("")
}

if c.ClusterURL == "" {
_ = c.GetClusterURL()
if err := viper.Unmarshal(c); err != nil {
fatal(fmt.Sprintf("Could not read config because %s.", err))
}
mutex.Unlock()
}

func absPathify(inPath string) string {
Expand Down
12 changes: 7 additions & 5 deletions cmd/server/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ func (h *Handler) createRS256KeysIfNotExist(c *config.Config, set, lookup string
generator := jwk.RS256Generator{}

if _, err := ctx.KeyManager.GetKey(set, lookup); errors.Is(err, pkg.ErrNotFound) {
logrus.Warnf("Key pair for signing %s is missing. Creating new one.", set)
logrus.Infof("Key pair for signing %s is missing. Creating new one.", set)

keys, err := generator.Generate("")
pkg.Must(err, "Could not generate %s key: %s", set, err)
Expand Down Expand Up @@ -127,8 +127,10 @@ func (h *Handler) createRootIfNewInstall(c *config.Config) {
c.ClientSecret = string(secret)
c.Unlock()

logrus.Warn("Temporary root client created.")
logrus.Warnf("client_id: %s", root.GetID())
logrus.Warnf("client_secret: %s", string(secret))
logrus.Warn("The root client must be removed in production. The root's credentials could be accidentally logged.")
logrus.Infoln("Temporary root client created.")
if forceRoot == "" {
logrus.Infoln("client_id: %s", root.GetID())
logrus.Infoln("client_secret: %s", string(secret))
logrus.Warn("WARNING: YOU MUST delete this client once in production, as credentials may have been leaked logfiles.")
}
}
8 changes: 3 additions & 5 deletions cmd/server/handler_oauth2_factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ package server
import (
"net/url"

"time"

"github.com/Sirupsen/logrus"
"github.com/go-errors/errors"
"github.com/julienschmidt/httprouter"
Expand Down Expand Up @@ -89,7 +87,7 @@ func newOAuth2Handler(c *config.Config, router *httprouter.Router, km jwk.Manage
keys, err = new(jwk.RS256Generator).Generate("")
pkg.Must(err, "Could not generate signing key for OpenID Connect")
km.AddKeySet(oauth2.OpenIDConnectKeyName, keys)
logrus.Warnln("Keypair generated.")
logrus.Infoln("Keypair generated.")
logrus.Warnln("WARNING: Automated key creation causes low entropy. Replace the keys as soon as possible.")
} else {
pkg.Must(err, "Could not fetch signing key for OpenID Connect")
Expand Down Expand Up @@ -177,8 +175,8 @@ func newOAuth2Handler(c *config.Config, router *httprouter.Router, km jwk.Manage
Consent: &oauth2.DefaultConsentStrategy{
Issuer: c.Issuer,
KeyManager: km,
DefaultChallengeLifespan: time.Hour,
DefaultIDTokenLifespan: time.Hour * 24,
DefaultChallengeLifespan: c.GetChallengeTokenLifespan(),
DefaultIDTokenLifespan: c.GetIDTokenLifespan(),
},
ConsentURL: *consentURL,
}
Expand Down
2 changes: 1 addition & 1 deletion cmd/token_user.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ var tokenUserCmd = &cobra.Command{
}

if r.URL.Query().Get("state") != string(state) {
message := fmt.Sprintf("States do not match. Expected %s but got %s", string(state), r.URL.Query().Get("state"))
message := fmt.Sprintf("States do not match. Expected %s, got %s", string(state), r.URL.Query().Get("state"))
fmt.Println(message)

w.WriteHeader(http.StatusInternalServerError)
Expand Down
Loading

0 comments on commit 0195703

Please sign in to comment.