Skip to content

Commit

Permalink
proxy: Improve compatibility with ORY Hydra 1.0.0-beta.8 (#108)
Browse files Browse the repository at this point in the history
This patch improves compatibility with ORY Hydra 1.0.0-beta.8 and updates vendored dependencies.

Closes #101

Signed-off-by: aeneasr <aeneas.rekkas@serlo.org>
  • Loading branch information
arekkas committed Aug 22, 2018
1 parent c5ab0c3 commit 296e012
Show file tree
Hide file tree
Showing 10 changed files with 315 additions and 159 deletions.
5 changes: 2 additions & 3 deletions Gopkg.lock

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

23 changes: 23 additions & 0 deletions UPGRADE.md
Expand Up @@ -18,6 +18,29 @@ before finalizing the upgrade process.

## 1.0.0-rc.1

### Configuration changes

To improve compatibility with ORY Hydra v1.0.0-beta.8, which introduces the public and admin endpoint, the following
environment variables have now been made optional:

- `CREDENTIALS_ISSUER_ID_TOKEN_HYDRA_CLIENT_ID`
- `CREDENTIALS_ISSUER_ID_TOKEN_HYDRA_CLIENT_SECRET`
- `CREDENTIALS_ISSUER_ID_TOKEN_HYDRA_CLIENT_SCOPES`
- `AUTHENTICATOR_OAUTH2_INTROSPECTION_CLIENT_ID`
- `AUTHENTICATOR_OAUTH2_INTROSPECTION_CLIENT_SECRET`
- `AUTHENTICATOR_OAUTH2_INTROSPECTION_TOKEN_URL`
- `AUTHENTICATOR_OAUTH2_INTROSPECTION_SCOPE`

They are optional because ORY Hydra's administrative endpoints no longer require authorization as they now
run on a privileged port. If you are running ORY Hydra behind a firewall that requires OAuth 2.0 Access tokens,
or you are using another OAuth 2.0 Server that requires an access token, you can still use these settings.

And the following environment variables have changed:

- `CREDENTIALS_ISSUER_ID_TOKEN_HYDRA_URL` is now `CREDENTIALS_ISSUER_ID_TOKEN_HYDRA_ADMIN_URL` and
`CREDENTIALS_ISSUER_ID_TOKEN_HYDRA_PUBLIC_URL` if ORY Hydra is protected with OAuth 2.0.
- `AUTHENTICATOR_OAUTH2_INTROSPECTION_INTROSPECT_URL` is now `AUTHENTICATOR_OAUTH2_INTROSPECTION_URL`.

### CORS is disabled by default

A new environment variable `CORS_ENABLED` was introduced. It sets whether CORS is enabled ("true") or not ("false")".
Expand Down
34 changes: 30 additions & 4 deletions cmd/helper_messages.go
Expand Up @@ -129,10 +129,9 @@ var credentialsIssuer = `CREDENTIALS ISSUERS
ORY-HYDRA ALGORITHM
===============
- CREDENTIALS_ISSUER_ID_TOKEN_HYDRA_URL: The URL where ORY Hydra is located.
--------------------------------------------------------------
Example: CREDENTIALS_ISSUER_ID_TOKEN_HYDRA_URL=http://hydra-url/
- CREDENTIALS_ISSUER_ID_TOKEN_HYDRA_ADMIN_URL: The URL where ORY Hydra's Admin API is located.
--------------------------------------------------------------
Example: CREDENTIALS_ISSUER_ID_TOKEN_HYDRA_ADMIN_URL=http://hydra-url/
- CREDENTIALS_ISSUER_ID_TOKEN_HYDRA_REFRESH_INTERVAL: This value sets how often ORY Oathkeeper checks if a new
key for signing is available at ORY Hydra. Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".
Expand All @@ -144,7 +143,34 @@ var credentialsIssuer = `CREDENTIALS ISSUERS
store, and retrieve the JSON Web Key from ORY Hydra.
--------------------------------------------------------------
Default: CREDENTIALS_ISSUER_ID_TOKEN_HYDRA_JWK_SET_ID=oathkeeper:id-token
--------------------------------------------------------------`
--------------------------------------------------------------
If ORY Hydra's Admin API itself is protected with OAuth 2.0, you can provide the access credentials to perform
an OAuth 2.0 Client Credentials flow before accessing ORY Hydra's APIs.
These settings are usually not required and an optional! If you don't need this feature, leave them undefined.
- CREDENTIALS_ISSUER_ID_TOKEN_HYDRA_CLIENT_ID: The ID of the OAuth 2.0 Client.
--------------------------------------------------------------
Example: CREDENTIALS_ISSUER_ID_TOKEN_HYDRA_CLIENT_ID=my-client
--------------------------------------------------------------
- CREDENTIALS_ISSUER_ID_TOKEN_HYDRA_CLIENT_SECRET: The secret of the OAuth 2.0 Client.
--------------------------------------------------------------
Example: CREDENTIALS_ISSUER_ID_TOKEN_HYDRA_CLIENT_SECRET=my-secret
--------------------------------------------------------------
- CREDENTIALS_ISSUER_ID_TOKEN_HYDRA_CLIENT_SCOPES: The OAuth 2.0 Scope the client should request.
--------------------------------------------------------------
Example: CREDENTIALS_ISSUER_ID_TOKEN_HYDRA_CLIENT_SCOPES=foo,bar
--------------------------------------------------------------
- CREDENTIALS_ISSUER_ID_TOKEN_HYDRA_PUBLIC_URL: The public URL where endpoint /oauth2/token is located.
--------------------------------------------------------------
Example: CREDENTIALS_ISSUER_ID_TOKEN_HYDRA_PUBLIC_URL=http://hydra-url/
--------------------------------------------------------------
`

func fatalf(msg string, args ...interface{}) {
fmt.Printf(msg+"\n", args...)
Expand Down
13 changes: 7 additions & 6 deletions cmd/helper_server.go
Expand Up @@ -39,7 +39,8 @@ func getHydraSDK() hydra.SDK {
sdk, err := hydra.NewSDK(&hydra.Configuration{
ClientID: viper.GetString("CREDENTIALS_ISSUER_ID_TOKEN_HYDRA_CLIENT_ID"),
ClientSecret: viper.GetString("CREDENTIALS_ISSUER_ID_TOKEN_HYDRA_CLIENT_SECRET"),
EndpointURL: viper.GetString("CREDENTIALS_ISSUER_ID_TOKEN_HYDRA_URL"),
AdminURL: viper.GetString("CREDENTIALS_ISSUER_ID_TOKEN_HYDRA_ADMIN_URL"),
PublicURL: viper.GetString("CREDENTIALS_ISSUER_ID_TOKEN_HYDRA_PUBLIC_URL"),
Scopes: strings.Split(viper.GetString("CREDENTIALS_ISSUER_ID_TOKEN_HYDRA_CLIENT_SCOPES"), ","),
})

Expand Down Expand Up @@ -181,11 +182,11 @@ func handlerFactories(keyManager rsakey.Manager) ([]proxy.Authenticator, []proxy
proxy.NewAuthenticatorNoOp(),
proxy.NewAuthenticatorAnonymous(viper.GetString("AUTHENTICATOR_ANONYMOUS_USERNAME")),
proxy.NewAuthenticatorOAuth2Introspection(
viper.GetString("AUTHENTICATOR_OAUTH2_INTROSPECTION_CLIENT_ID"),
viper.GetString("AUTHENTICATOR_OAUTH2_INTROSPECTION_CLIENT_SECRET"),
viper.GetString("AUTHENTICATOR_OAUTH2_INTROSPECTION_TOKEN_URL"),
viper.GetString("AUTHENTICATOR_OAUTH2_INTROSPECTION_INTROSPECT_URL"),
strings.Split(viper.GetString("AUTHENTICATOR_OAUTH2_INTROSPECTION_SCOPE"), ","),
viper.GetString("AUTHENTICATOR_OAUTH2_INTROSPECTION_AUTHORIZATION_CLIENT_ID"),
viper.GetString("AUTHENTICATOR_OAUTH2_INTROSPECTION_AUTHORIZATION_CLIENT_SECRET"),
viper.GetString("AUTHENTICATOR_OAUTH2_INTROSPECTION_AUTHORIZATION_TOKEN_URL"),
viper.GetString("AUTHENTICATOR_OAUTH2_INTROSPECTION_URL"),
strings.Split(viper.GetString("AUTHENTICATOR_OAUTH2_INTROSPECTION_AUTHORIZATION_SCOPE"), ","),
fosite.WildcardScopeStrategy,
),
proxy.NewAuthenticatorOAuth2ClientCredentials(
Expand Down
48 changes: 27 additions & 21 deletions cmd/serve_proxy.go
Expand Up @@ -93,33 +93,39 @@ AUTHENTICATORS
--------------------------------------------------------------
- OAuth 2.0 Token Introspection Authenticator:
- AUTHENTICATOR_OAUTH2_INTROSPECTION_CLIENT_ID: The OAuth 2.0 Client ID the client that performs the OAuth 2.0
Token Introspection. The OAuth 2.0 Token Introspection endpoint is typically protected and requires a valid
OAuth 2.0 Client in order to check if a token is valid or not.
- AUTHENTICATOR_OAUTH2_INTROSPECTION_INTROSPECT_URL: The OAuth 2.0 Token Introspection URL.
--------------------------------------------------------------
Example: AUTHENTICATOR_OAUTH2_INTROSPECTION_CLIENT_ID=my-client-id
Example: AUTHENTICATOR_OAUTH2_INTROSPECTION_INTROSPECT_URL=http://my-oauth2-server/oauth2/introspect
--------------------------------------------------------------
- AUTHENTICATOR_OAUTH2_INTROSPECTION_CLIENT_SECRET:T he OAuth 2.0 Client Secret of the client that performs the OAuth 2.0 Token Introspection.
--------------------------------------------------------------
Example: AUTHENTICATOR_OAUTH2_INTROSPECTION_CLIENT_ID=my-client-secret
--------------------------------------------------------------
If the OAuth 2.0 Token Introspection Endpoint itself is protected with OAuth 2.0, you can provide the access credentials to perform
an OAuth 2.0 Client Credentials flow before accessing ORY Hydra's APIs.
- AUTHENTICATOR_OAUTH2_INTROSPECTION_TOKEN_URL: The OAuth 2.0 Token URL.
--------------------------------------------------------------
Example: AUTHENTICATOR_OAUTH2_INTROSPECTION_TOKEN_URL=http://my-oauth2-server/oauth2/token
--------------------------------------------------------------
These settings are usually not required and an optional! If you don't need this feature, leave them undefined.
- AUTHENTICATOR_OAUTH2_INTROSPECTION_INTROSPECT_URL: The OAuth 2.0 Token Introspection URL.
--------------------------------------------------------------
Example: AUTHENTICATOR_OAUTH2_INTROSPECTION_INTROSPECT_URL=http://my-oauth2-server/oauth2/introspect
--------------------------------------------------------------
- AUTHENTICATOR_OAUTH2_INTROSPECTION_SCOPE: If the OAuth 2.0 Token Introspection endpoint requires a certain OAuth 2.0 Scope
in order to be accessed, you can set it using this environment variable. Use commas to define more than one OAuth 2.0 Scope.
--------------------------------------------------------------
Example: AUTHENTICATOR_OAUTH2_INTROSPECTION_SCOPE=scope-a,scope-b
--------------------------------------------------------------
- AUTHENTICATOR_OAUTH2_INTROSPECTION_CLIENT_ID: The OAuth 2.0 Client ID the client that performs the OAuth 2.0
Token Introspection. The OAuth 2.0 Token Introspection endpoint is typically protected and requires a valid
OAuth 2.0 Client in order to check if a token is valid or not.
--------------------------------------------------------------
Example: AUTHENTICATOR_OAUTH2_INTROSPECTION_CLIENT_ID=my-client-id
--------------------------------------------------------------
- AUTHENTICATOR_OAUTH2_INTROSPECTION_CLIENT_SECRET: The OAuth 2.0 Client Secret of the client that performs the OAuth 2.0 Token Introspection.
--------------------------------------------------------------
Example: AUTHENTICATOR_OAUTH2_INTROSPECTION_CLIENT_ID=my-client-secret
--------------------------------------------------------------
- AUTHENTICATOR_OAUTH2_INTROSPECTION_TOKEN_URL: The OAuth 2.0 Token URL.
--------------------------------------------------------------
Example: AUTHENTICATOR_OAUTH2_INTROSPECTION_TOKEN_URL=http://my-oauth2-server/oauth2/token
--------------------------------------------------------------
- AUTHENTICATOR_OAUTH2_INTROSPECTION_SCOPE: If the OAuth 2.0 Token Introspection endpoint requires a certain OAuth 2.0 Scope
in order to be accessed, you can set it using this environment variable. Use commas to define more than one OAuth 2.0 Scope.
--------------------------------------------------------------
Example: AUTHENTICATOR_OAUTH2_INTROSPECTION_SCOPE=scope-a,scope-b
--------------------------------------------------------------
AUTHORIZERS
Expand Down
2 changes: 0 additions & 2 deletions proxy/authenticator_oauth2_client_credentials.go
Expand Up @@ -11,7 +11,6 @@ import (
"github.com/ory/oathkeeper/helper"
"github.com/ory/oathkeeper/rule"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"golang.org/x/oauth2/clientcredentials"
)

Expand Down Expand Up @@ -63,7 +62,6 @@ func (a *AuthenticatorOAuth2ClientCredentials) Authenticate(r *http.Request, con
return nil, errors.Wrapf(helper.ErrUnauthorized, err.Error())
}

logrus.New().Printf("Got wow user pw, %s, %s", user, password)
c := &clientcredentials.Config{
ClientID: user,
ClientSecret: password,
Expand Down
84 changes: 57 additions & 27 deletions proxy/authenticator_oauth2_introspection.go
Expand Up @@ -6,11 +6,17 @@ import (
"fmt"
"net/http"

"context"
"net/url"
"strings"

"github.com/ory/fosite"
"github.com/ory/keto/authentication"
"github.com/ory/go-convenience/stringslice"
"github.com/ory/hydra/sdk/go/hydra/swagger"
"github.com/ory/oathkeeper/helper"
"github.com/ory/oathkeeper/rule"
"github.com/pkg/errors"
"golang.org/x/oauth2/clientcredentials"
)

type AuthenticatorOAuth2IntrospectionConfiguration struct {
Expand All @@ -27,18 +33,24 @@ type AuthenticatorOAuth2IntrospectionConfiguration struct {
}

type AuthenticatorOAuth2Introspection struct {
helper authenticatorOAuth2IntrospectionHelper
client *http.Client
introspectionURL string
scopeStrategy fosite.ScopeStrategy
}

type authenticatorOAuth2IntrospectionHelper interface {
Introspect(token string, scopes []string, strategy fosite.ScopeStrategy) (*authentication.IntrospectionResponse, error)
}

func NewAuthenticatorOAuth2Introspection(clientID, clientSecret, tokenURL, introspectionURL string, scopes []string, strategy fosite.ScopeStrategy) *AuthenticatorOAuth2Introspection {
c := http.DefaultClient
if len(clientID)+len(clientSecret)+len(tokenURL)+len(scopes) > 0 {
c = (&clientcredentials.Config{
ClientID: clientID,
ClientSecret: clientSecret,
TokenURL: tokenURL,
Scopes: scopes,
}).Client(context.Background())
}

return &AuthenticatorOAuth2Introspection{
helper: authentication.NewOAuth2IntrospectionAuthentication(clientID, clientSecret, tokenURL, introspectionURL, scopes, strategy),
client: c,
introspectionURL: introspectionURL,
}
}
Expand All @@ -65,42 +77,60 @@ func (a *AuthenticatorOAuth2Introspection) Authenticate(r *http.Request, config
return nil, errors.WithStack(ErrAuthenticatorNotResponsible)
}

ir, err := a.helper.Introspect(token, cf.Scopes, a.scopeStrategy)
body := url.Values{"token": {token}, "scope": {strings.Join(cf.Scopes, " ")}}
resp, err := a.client.Post(a.introspectionURL, "application/x-www-form-urlencoded", strings.NewReader(body.Encode()))
if err != nil {
return nil, err
return nil, errors.WithStack(err)
}
defer resp.Body.Close()

if resp.StatusCode != http.StatusOK {
return nil, errors.Errorf("Introspection returned status code %d but expected %d", resp.StatusCode, http.StatusOK)
}

var ir swagger.OAuth2TokenIntrospection
if err := json.NewDecoder(resp.Body).Decode(&ir); err != nil {
return nil, errors.WithStack(err)
}

if len(ir.TokenType) > 0 && ir.TokenType != "access_token" {
return nil, errors.WithStack(helper.ErrForbidden.WithReason(fmt.Sprintf("Introspected token is not an access token but \"%s\"", ir.TokenType)))
}

if !ir.Active {
return nil, errors.WithStack(helper.ErrForbidden.WithReason("Access token introspection says token is not active"))
}

for _, audience := range cf.Audience {
if !stringInSlice(audience, ir.Audience) {
if !stringslice.Has(ir.Aud, audience) {
return nil, errors.WithStack(helper.ErrForbidden.WithReason(fmt.Sprintf("Token audience is not intended for target audience %s", audience)))
}
}

if len(cf.Issuers) > 0 {
if !stringInSlice(ir.Issuer, cf.Issuers) {
if !stringslice.Has(cf.Issuers, ir.Iss) {
return nil, errors.WithStack(helper.ErrForbidden.WithReason(fmt.Sprintf("Token issuer does not match any trusted issuer")))
}
}

if len(ir.Extra) == 0 {
ir.Extra = map[string]interface{}{}
if a.scopeStrategy != nil {
for _, scope := range cf.Scopes {
if !a.scopeStrategy(strings.Split(ir.Scope, " "), scope) {
return nil, errors.WithStack(helper.ErrForbidden.WithReason(fmt.Sprintf("Scope %s was not granted", scope)))
}
}
}

ir.Extra["username"] = ir.Username
ir.Extra["client_id"] = ir.ClientID
ir.Extra["scope"] = ir.Scope
if len(ir.Ext) == 0 {
ir.Ext = map[string]interface{}{}
}

ir.Ext["username"] = ir.Username
ir.Ext["client_id"] = ir.ClientId
ir.Ext["scope"] = ir.Scope

return &AuthenticationSession{
Subject: ir.Subject,
Extra: ir.Extra,
Subject: ir.Sub,
Extra: ir.Ext,
}, nil
}

func stringInSlice(needle string, haystack []string) bool {
for _, b := range haystack {
if b == needle {
return true
}
}
return false
}
42 changes: 0 additions & 42 deletions proxy/authenticator_oauth2_introspection_mock.go

This file was deleted.

0 comments on commit 296e012

Please sign in to comment.