Skip to content

Commit

Permalink
cmd: Introduce public and administrative ports
Browse files Browse the repository at this point in the history
This patch introduces two ports, public and administrative. The public
port is responsible for handling API requests to public endpoints such
as /oauth2/auth, while the administrative port handles requests to
JWK, OAuth 2.0 Client, and Login & Consent endpoints.

Closes ory#904

Signed-off-by: arekkas <aeneas@ory.am>
  • Loading branch information
arekkas authored and arekkas committed Aug 6, 2018
1 parent 8632a2e commit cfee3eb
Show file tree
Hide file tree
Showing 23 changed files with 525 additions and 248 deletions.
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,4 @@ COPY --from=0 /go/src/github.com/ory/hydra/hydra /usr/bin/hydra

ENTRYPOINT ["hydra"]

CMD ["serve"]
CMD ["serve all"]
2 changes: 1 addition & 1 deletion Dockerfile-alpine
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,4 @@ COPY --from=0 /go/src/github.com/ory/hydra/hydra /usr/bin/hydra

ENTRYPOINT ["hydra"]

CMD ["serve"]
CMD ["serve all"]
9 changes: 9 additions & 0 deletions UPGRADE.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,15 @@ before finalizing the upgrade process.
This patch introduces some minor database schema changes. Before you apply it, you must run `hydra migrate sql` against
your database.

### Subcommands `admin`, `public`, `all` have been added to `hydra serve`

With this patch, ORY Hydra exposes two ports:

- Public API (default port 4444) handles requests coming from the public internet, like OAuth 2.0 Authorization
and Token requests, OpenID Connect UserInfo, OAuth 2.0 Token Revokation, and OpenID Connect Discovery.
- Administrative API (default port 4445) handles administrative requests like managing OAuth 2.0 Clients,
JSON Web Keys, login and consent sessions, and others.

### OAuth 2.0 Client flag `public` has been removed

Previously, OAuth 2.0 Clients had a flag called `public`. If set to true, the OAuth 2.0 Client was able to exchange
Expand Down
16 changes: 11 additions & 5 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,17 @@ func initConfig() {
}
viper.AutomaticEnv() // read in environment variables that match

viper.BindEnv("HOST")
viper.SetDefault("HOST", "")
viper.BindEnv("PUBLIC_HOST")
viper.SetDefault("PUBLIC_HOST", "")

viper.BindEnv("ADMIN_HOST")
viper.SetDefault("ADMIN_HOST", "")

viper.BindEnv("PUBLIC_PORT")
viper.SetDefault("PUBLIC_PORT", 4444)

viper.BindEnv("ADMIN_PORT")
viper.SetDefault("ADMIN_PORT", 4445)

viper.BindEnv("CLIENT_ID")
viper.SetDefault("CLIENT_ID", "")
Expand Down Expand Up @@ -137,9 +146,6 @@ func initConfig() {
viper.BindEnv("OAUTH2_ACCESS_TOKEN_STRATEGY")
viper.SetDefault("OAUTH2_ACCESS_TOKEN_STRATEGY", "opaque")

viper.BindEnv("PORT")
viper.SetDefault("PORT", 4444)

viper.BindEnv("OAUTH2_ISSUER_URL")
viper.SetDefault("OAUTH2_ISSUER_URL", "http://localhost:4444")

Expand Down
57 changes: 33 additions & 24 deletions cmd/root_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,33 +32,41 @@ import (
"github.com/stretchr/testify/assert"
)

var port int
var frontendPort, backendPort int

func init() {
var err error
port, err = freeport.GetFreePort()
frontendPort, err = freeport.GetFreePort()
if err != nil {
panic(err.Error())
}
os.Setenv("PORT", fmt.Sprintf("%d", port))

backendPort, err = freeport.GetFreePort()
if err != nil {
panic(err.Error())
}

os.Setenv("PUBLIC_PORT", fmt.Sprintf("%d", frontendPort))
os.Setenv("ADMIN_PORT", fmt.Sprintf("%d", backendPort))
os.Setenv("DATABASE_URL", "memory")
os.Setenv("HYDRA_URL", fmt.Sprintf("https://localhost:%d/", port))
os.Setenv("OAUTH2_ISSUER_URL", fmt.Sprintf("https://localhost:%d/", port))
//os.Setenv("HYDRA_URL", fmt.Sprintf("https://localhost:%d/", frontendPort))
os.Setenv("OAUTH2_ISSUER_URL", fmt.Sprintf("https://localhost:%d/", frontendPort))
}

func TestExecute(t *testing.T) {
var osArgs = make([]string, len(os.Args))
copy(osArgs, os.Args)

endpoint := fmt.Sprintf("https://localhost:%d/", port)
frontend := fmt.Sprintf("https://localhost:%d/", frontendPort)
backend := fmt.Sprintf("https://localhost:%d/", backendPort)

for _, c := range []struct {
args []string
wait func() bool
expectErr bool
}{
{
args: []string{"serve", "--disable-telemetry"},
args: []string{"serve", "all", "--disable-telemetry"},
wait: func() bool {
client := &http.Client{
Transport: &transporter{
Expand All @@ -69,31 +77,32 @@ func TestExecute(t *testing.T) {
},
}

_, err := client.Get(fmt.Sprintf("https://127.0.0.1:%d/health/status", port))
_, err := client.Get(fmt.Sprintf("https://127.0.0.1:%d/health/status", frontendPort))
if err != nil {
t.Logf("HTTP request failed: %s", err)
} else {
// Give a bit more time to initialize
time.Sleep(time.Second * 5)
}
return err != nil
},
},
{args: []string{"clients", "create", "--endpoint", endpoint, "--id", "foobarbaz", "--secret", "foobar", "-g", "client_credentials"}},
{args: []string{"clients", "get", "--endpoint", endpoint, "foobarbaz"}},
{args: []string{"clients", "create", "--endpoint", endpoint, "--id", "public-foo"}},
{args: []string{"clients", "delete", "--endpoint", endpoint, "public-foo"}},
{args: []string{"keys", "create", "foo", "--endpoint", endpoint, "-a", "HS256"}},
{args: []string{"keys", "get", "--endpoint", endpoint, "foo"}},
{args: []string{"keys", "rotate", "--endpoint", endpoint, "foo"}},
{args: []string{"keys", "get", "--endpoint", endpoint, "foo"}},
{args: []string{"keys", "delete", "--endpoint", endpoint, "foo"}},
{args: []string{"keys", "import", "--endpoint", endpoint, "import-1", "../test/stub/ecdh.key", "../test/stub/ecdh.pub"}},
{args: []string{"keys", "import", "--endpoint", endpoint, "import-2", "../test/stub/rsa.key", "../test/stub/rsa.pub"}},
{args: []string{"token", "revoke", "--endpoint", endpoint, "--client-secret", "foobar", "--client-id", "foobarbaz", "foo"}},
{args: []string{"token", "client", "--endpoint", endpoint, "--client-secret", "foobar", "--client-id", "foobarbaz"}},
{args: []string{"clients", "create", "--endpoint", backend, "--id", "foobarbaz", "--secret", "foobar", "-g", "client_credentials"}},
{args: []string{"clients", "get", "--endpoint", backend, "foobarbaz"}},
{args: []string{"clients", "create", "--endpoint", backend, "--id", "public-foo"}},
{args: []string{"clients", "delete", "--endpoint", backend, "public-foo"}},
{args: []string{"keys", "create", "foo", "--endpoint", backend, "-a", "HS256"}},
{args: []string{"keys", "get", "--endpoint", backend, "foo"}},
{args: []string{"keys", "rotate", "--endpoint", backend, "foo"}},
{args: []string{"keys", "get", "--endpoint", backend, "foo"}},
{args: []string{"keys", "delete", "--endpoint", backend, "foo"}},
{args: []string{"keys", "import", "--endpoint", backend, "import-1", "../test/stub/ecdh.key", "../test/stub/ecdh.pub"}},
{args: []string{"keys", "import", "--endpoint", backend, "import-2", "../test/stub/rsa.key", "../test/stub/rsa.pub"}},
{args: []string{"token", "revoke", "--endpoint", frontend, "--client-secret", "foobar", "--client-id", "foobarbaz", "foo"}},
{args: []string{"token", "client", "--endpoint", frontend, "--client-secret", "foobar", "--client-id", "foobarbaz"}},
{args: []string{"help", "migrate", "sql"}},
{args: []string{"version"}},
{args: []string{"token", "flush", "--endpoint", endpoint}},
{args: []string{"token", "flush", "--endpoint", backend}},
} {
c.args = append(c.args, []string{"--skip-tls-verify"}...)
RootCmd.SetArgs(c.args)
Expand All @@ -110,10 +119,10 @@ func TestExecute(t *testing.T) {
for c.wait() {
t.Logf("Config file has not been found yet, retrying attempt #%d...", count)
count++
if count > 200 {
if count > 15 {
t.FailNow()
}
time.Sleep(time.Second * 2)
time.Sleep(time.Second)
}
} else {
err := RootCmd.Execute()
Expand Down
52 changes: 29 additions & 23 deletions cmd/serve.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,25 +24,10 @@ import (
"os"
"strconv"

"github.com/ory/hydra/cmd/server"
"github.com/spf13/cobra"
)

// serveCmd represents the host command
var serveCmd = &cobra.Command{
Use: "serve",
Short: "Start the HTTP/2 host service",
Long: `Starts all HTTP/2 APIs and connects to a database backend.
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
const serveControls = `CORE CONTROLS
=============
- DATABASE_URL: A URL to a persistent backend. Hydra supports various backends:
Expand Down Expand Up @@ -208,8 +193,29 @@ DEBUG CONTROLS
- PROFILING: Set "PROFILING=cpu" to enable cpu profiling and "PROFILING=memory" to enable memory profiling.
It is not possible to do both at the same time.
Example: PROFILING=cpu
`,
Run: server.RunHost(c),
`

// serveCmd represents the host command
var serveCmd = &cobra.Command{
Use: "serve",
Short: "Parent command for starting public and administrative HTTP/2 APIs",
Long: `ORY Hydra exposes two ports, a public and an administrative port. The public port is responsible
for handling requests from the public internet, such as the OAuth 2.0 Authorize and Token URLs. The administrative
port handles administrative requests like creating OAuth 2.0 Clients, managing JSON Web Keys, and managing User Login
and Consent sessions.
It is recommended to run "hydra serve all". If you need granular control over CORS settings or similar, you may
want to run "hydra serve admin" and "admin serve public" separately.
To learn more about each individual command, run:
- hydra help serve all
- hydra help serve admin
- hydra help serve public
All sub-commands share command line flags and the following environment variable names:
` + serveControls,
}

func init() {
Expand All @@ -223,12 +229,12 @@ func init() {

// Cobra supports local flags which will only run when this command
// is called directly, e.g.:
serveCmd.Flags().BoolVar(&c.ForceHTTP, "dangerous-force-http", false, "Disable HTTP/2 over TLS (HTTPS) and serve HTTP instead. Never use this in production.")
//serveCmd.Flags().Bool("dangerous-auto-logon", false, "Stores the root credentials in ~/.hydra.yml. Do not use in production.")
serveCmd.PersistentFlags().BoolVar(&c.ForceHTTP, "dangerous-force-http", false, "Disable HTTP/2 over TLS (HTTPS) and serve HTTP instead. Never use this in production.")
//serveCmd.PersistentFlags().Bool("dangerous-auto-logon", false, "Stores the root credentials in ~/.hydra.yml. Do not use in production.")

disableTelemetryEnv, _ := strconv.ParseBool(os.Getenv("DISABLE_TELEMETRY"))
serveCmd.Flags().Bool("disable-telemetry", disableTelemetryEnv, "Disable anonymized telemetry reports - for more information please visit https://www.ory.sh/docs/guides/telemetry")
serveCmd.PersistentFlags().Bool("disable-telemetry", disableTelemetryEnv, "Disable anonymized telemetry reports - for more information please visit https://www.ory.sh/docs/guides/telemetry")

serveCmd.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.")
serveCmd.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.")
serveCmd.PersistentFlags().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.")
serveCmd.PersistentFlags().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.")
}
53 changes: 53 additions & 0 deletions cmd/serve_admin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// Copyright © 2018 NAME HERE <EMAIL ADDRESS>
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package cmd

import (
"github.com/ory/hydra/cmd/server"
"github.com/spf13/cobra"
)

// adminCmd represents the admin command
var adminCmd = &cobra.Command{
Use: "admin",
Short: "Serves Administrative HTTP/2 APIs",
Long: `This command opens one port and listens to HTTP/2 API requests. The exposed API handles administrative
requests like managing OAuth 2.0 Clients, JSON Web Keys, login and consent sessions, and others.
This command is configurable using the same options available to "serve public" and "serve all".
It is generally recommended to use this command only if you require granular control over the administrative and public APIs.
For example, you might want to run different TLS certificates or CORS settings on the public and administrative API.
This command does not work with the "memory" database. Both services (administrative, public) MUST use the same database
connection to be able to synchronize.
` + serveControls,
Run: server.RunServeAdmin(c),
}

func init() {
serveCmd.AddCommand(adminCmd)

// Here you will define your flags and configuration settings.

// Cobra supports Persistent Flags which will work for this command
// and all subcommands, e.g.:
// adminCmd.PersistentFlags().String("foo", "", "A help for foo")

// Cobra supports local flags which will only run when this command
// is called directly, e.g.:
// adminCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
}
54 changes: 54 additions & 0 deletions cmd/serve_all.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// Copyright © 2018 NAME HERE <EMAIL ADDRESS>
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package cmd

import (
"github.com/ory/hydra/cmd/server"
"github.com/spf13/cobra"
)

// allCmd represents the all command
var allCmd = &cobra.Command{
Use: "all",
Short: "Serves both public and administrative HTTP/2 APIs",
Long: `Starts a process which listens on two ports for public and administrative HTTP/2 API requests.
If you want more granular control (e.g. different TLS settings) over each API group (administrative, public) you
can run "serve admin" and "serve public" separately.
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. This command exposes exposes command line flags, which are listed below
the controls section.
` + serveControls,
Run: server.RunServeAll(c),
}

func init() {
serveCmd.AddCommand(allCmd)

// Here you will define your flags and configuration settings.

// Cobra supports Persistent Flags which will work for this command
// and all subcommands, e.g.:
// allCmd.PersistentFlags().String("foo", "", "A help for foo")

// Cobra supports local flags which will only run when this command
// is called directly, e.g.:
// allCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
}
Loading

0 comments on commit cfee3eb

Please sign in to comment.