Fix: ‘autoRedirect’ hardcode ‘https’ scheme
Signed-off-by: icefed <zlwangel@gmail.com>
This commit is contained in:
parent
51a72c2aef
commit
63eb22d74b
3 changed files with 157 additions and 29 deletions
|
@ -591,7 +591,8 @@ security.
|
||||||
| `service` | yes | The service being authenticated. |
|
| `service` | yes | The service being authenticated. |
|
||||||
| `issuer` | yes | The name of the token issuer. The issuer inserts this into the token so it must match the value configured for the issuer. |
|
| `issuer` | yes | The name of the token issuer. The issuer inserts this into the token so it must match the value configured for the issuer. |
|
||||||
| `rootcertbundle` | yes | The absolute path to the root certificate bundle. This bundle contains the public part of the certificates used to sign authentication tokens. |
|
| `rootcertbundle` | yes | The absolute path to the root certificate bundle. This bundle contains the public part of the certificates used to sign authentication tokens. |
|
||||||
| `autoredirect` | no | When set to `true`, `realm` will automatically be set using the Host header of the request as the domain and a path of `/auth/token/`|
|
| `autoredirect` | no | When set to `true`, `realm` will automatically be set using the Host header of the request as the domain and a path of `/auth/token/`(or specified by `autoredirectpath`), the `realm` URL Scheme will use `X-Forwarded-Proto` header if set, otherwise it will be set to `https`. |
|
||||||
|
| `autoredirectpath` | no | The path to redirect to if `autoredirect` is set to `true`, default: `/auth/token/`. |
|
||||||
|
|
||||||
|
|
||||||
For more information about Token based authentication configuration, see the
|
For more information about Token based authentication configuration, see the
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
@ -83,11 +84,12 @@ var (
|
||||||
|
|
||||||
// authChallenge implements the auth.Challenge interface.
|
// authChallenge implements the auth.Challenge interface.
|
||||||
type authChallenge struct {
|
type authChallenge struct {
|
||||||
err error
|
err error
|
||||||
realm string
|
realm string
|
||||||
autoRedirect bool
|
autoRedirect bool
|
||||||
service string
|
autoRedirectPath string
|
||||||
accessSet accessSet
|
service string
|
||||||
|
accessSet accessSet
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ auth.Challenge = authChallenge{}
|
var _ auth.Challenge = authChallenge{}
|
||||||
|
@ -102,13 +104,28 @@ func (ac authChallenge) Status() int {
|
||||||
return http.StatusUnauthorized
|
return http.StatusUnauthorized
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func buildAutoRedirectURL(r *http.Request, autoRedirectPath string) string {
|
||||||
|
scheme := "https"
|
||||||
|
|
||||||
|
if forwardedProto := r.Header.Get("X-Forwarded-Proto"); len(forwardedProto) > 0 {
|
||||||
|
scheme = forwardedProto
|
||||||
|
}
|
||||||
|
|
||||||
|
u := &url.URL{
|
||||||
|
Scheme: scheme,
|
||||||
|
Host: r.Host,
|
||||||
|
Path: autoRedirectPath,
|
||||||
|
}
|
||||||
|
return u.String()
|
||||||
|
}
|
||||||
|
|
||||||
// challengeParams constructs the value to be used in
|
// challengeParams constructs the value to be used in
|
||||||
// the WWW-Authenticate response challenge header.
|
// the WWW-Authenticate response challenge header.
|
||||||
// See https://tools.ietf.org/html/rfc6750#section-3
|
// See https://tools.ietf.org/html/rfc6750#section-3
|
||||||
func (ac authChallenge) challengeParams(r *http.Request) string {
|
func (ac authChallenge) challengeParams(r *http.Request) string {
|
||||||
var realm string
|
var realm string
|
||||||
if ac.autoRedirect {
|
if ac.autoRedirect {
|
||||||
realm = fmt.Sprintf("https://%s/auth/token", r.Host)
|
realm = buildAutoRedirectURL(r, ac.autoRedirectPath)
|
||||||
} else {
|
} else {
|
||||||
realm = ac.realm
|
realm = ac.realm
|
||||||
}
|
}
|
||||||
|
@ -134,23 +151,29 @@ func (ac authChallenge) SetHeaders(r *http.Request, w http.ResponseWriter) {
|
||||||
|
|
||||||
// accessController implements the auth.AccessController interface.
|
// accessController implements the auth.AccessController interface.
|
||||||
type accessController struct {
|
type accessController struct {
|
||||||
realm string
|
realm string
|
||||||
autoRedirect bool
|
autoRedirect bool
|
||||||
issuer string
|
autoRedirectPath string
|
||||||
service string
|
issuer string
|
||||||
rootCerts *x509.CertPool
|
service string
|
||||||
trustedKeys map[string]crypto.PublicKey
|
rootCerts *x509.CertPool
|
||||||
|
trustedKeys map[string]crypto.PublicKey
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
defaultAutoRedirectPath = "/auth/token"
|
||||||
|
)
|
||||||
|
|
||||||
// tokenAccessOptions is a convenience type for handling
|
// tokenAccessOptions is a convenience type for handling
|
||||||
// options to the contstructor of an accessController.
|
// options to the contstructor of an accessController.
|
||||||
type tokenAccessOptions struct {
|
type tokenAccessOptions struct {
|
||||||
realm string
|
realm string
|
||||||
autoRedirect bool
|
autoRedirect bool
|
||||||
issuer string
|
autoRedirectPath string
|
||||||
service string
|
issuer string
|
||||||
rootCertBundle string
|
service string
|
||||||
jwks string
|
rootCertBundle string
|
||||||
|
jwks string
|
||||||
}
|
}
|
||||||
|
|
||||||
// checkOptions gathers the necessary options
|
// checkOptions gathers the necessary options
|
||||||
|
@ -187,6 +210,19 @@ func checkOptions(options map[string]interface{}) (tokenAccessOptions, error) {
|
||||||
}
|
}
|
||||||
opts.autoRedirect = autoRedirect
|
opts.autoRedirect = autoRedirect
|
||||||
}
|
}
|
||||||
|
if opts.autoRedirect {
|
||||||
|
autoRedirectPathVal, ok := options["autoredirectpath"]
|
||||||
|
if ok {
|
||||||
|
autoRedirectPath, ok := autoRedirectPathVal.(string)
|
||||||
|
if !ok {
|
||||||
|
return opts, fmt.Errorf("token auth requires a valid option string: autoredirectpath")
|
||||||
|
}
|
||||||
|
opts.autoRedirectPath = autoRedirectPath
|
||||||
|
}
|
||||||
|
if opts.autoRedirectPath == "" {
|
||||||
|
opts.autoRedirectPath = defaultAutoRedirectPath
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return opts, nil
|
return opts, nil
|
||||||
}
|
}
|
||||||
|
@ -287,12 +323,13 @@ func newAccessController(options map[string]interface{}) (auth.AccessController,
|
||||||
}
|
}
|
||||||
|
|
||||||
return &accessController{
|
return &accessController{
|
||||||
realm: config.realm,
|
realm: config.realm,
|
||||||
autoRedirect: config.autoRedirect,
|
autoRedirect: config.autoRedirect,
|
||||||
issuer: config.issuer,
|
autoRedirectPath: config.autoRedirectPath,
|
||||||
service: config.service,
|
issuer: config.issuer,
|
||||||
rootCerts: rootPool,
|
service: config.service,
|
||||||
trustedKeys: trustedKeys,
|
rootCerts: rootPool,
|
||||||
|
trustedKeys: trustedKeys,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -300,10 +337,11 @@ func newAccessController(options map[string]interface{}) (auth.AccessController,
|
||||||
// for actions on resources described by the given access items.
|
// for actions on resources described by the given access items.
|
||||||
func (ac *accessController) Authorized(req *http.Request, accessItems ...auth.Access) (*auth.Grant, error) {
|
func (ac *accessController) Authorized(req *http.Request, accessItems ...auth.Access) (*auth.Grant, error) {
|
||||||
challenge := &authChallenge{
|
challenge := &authChallenge{
|
||||||
realm: ac.realm,
|
realm: ac.realm,
|
||||||
autoRedirect: ac.autoRedirect,
|
autoRedirect: ac.autoRedirect,
|
||||||
service: ac.service,
|
autoRedirectPath: ac.autoRedirectPath,
|
||||||
accessSet: newAccessSet(accessItems...),
|
service: ac.service,
|
||||||
|
accessSet: newAccessSet(accessItems...),
|
||||||
}
|
}
|
||||||
|
|
||||||
prefix, rawToken, ok := strings.Cut(req.Header.Get("Authorization"), " ")
|
prefix, rawToken, ok := strings.Cut(req.Header.Get("Authorization"), " ")
|
||||||
|
|
89
registry/auth/token/accesscontroller_test.go
Normal file
89
registry/auth/token/accesscontroller_test.go
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
package token
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestBuildAutoRedirectURL(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
reqGetter func() *http.Request
|
||||||
|
autoRedirectPath string
|
||||||
|
expectedURL string
|
||||||
|
}{{
|
||||||
|
name: "http",
|
||||||
|
reqGetter: func() *http.Request {
|
||||||
|
req := httptest.NewRequest("GET", "http://example.com/", nil)
|
||||||
|
return req
|
||||||
|
},
|
||||||
|
autoRedirectPath: "/auth",
|
||||||
|
expectedURL: "https://example.com/auth",
|
||||||
|
}, {
|
||||||
|
name: "x-forwarded",
|
||||||
|
reqGetter: func() *http.Request {
|
||||||
|
req := httptest.NewRequest("GET", "http://example.com/", nil)
|
||||||
|
req.Header.Set("X-Forwarded-Proto", "http")
|
||||||
|
return req
|
||||||
|
},
|
||||||
|
autoRedirectPath: "/auth/token",
|
||||||
|
expectedURL: "http://example.com/auth/token",
|
||||||
|
}}
|
||||||
|
|
||||||
|
for _, tc := range cases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
req := tc.reqGetter()
|
||||||
|
result := buildAutoRedirectURL(req, tc.autoRedirectPath)
|
||||||
|
if result != tc.expectedURL {
|
||||||
|
t.Errorf("expected %s, got %s", tc.expectedURL, result)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCheckOptions(t *testing.T) {
|
||||||
|
realm := "https://auth.example.com/token/"
|
||||||
|
issuer := "test-issuer.example.com"
|
||||||
|
service := "test-service.example.com"
|
||||||
|
|
||||||
|
options := map[string]interface{}{
|
||||||
|
"realm": realm,
|
||||||
|
"issuer": issuer,
|
||||||
|
"service": service,
|
||||||
|
"rootcertbundle": "",
|
||||||
|
"autoredirect": true,
|
||||||
|
"autoredirectpath": "/auth",
|
||||||
|
}
|
||||||
|
|
||||||
|
ta, err := checkOptions(options)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if ta.autoRedirect != true {
|
||||||
|
t.Fatal("autoredirect should be true")
|
||||||
|
}
|
||||||
|
if ta.autoRedirectPath != "/auth" {
|
||||||
|
t.Fatal("autoredirectpath should be /auth")
|
||||||
|
}
|
||||||
|
|
||||||
|
options = map[string]interface{}{
|
||||||
|
"realm": realm,
|
||||||
|
"issuer": issuer,
|
||||||
|
"service": service,
|
||||||
|
"rootcertbundle": "",
|
||||||
|
"autoredirect": true,
|
||||||
|
"autoredirectforcetlsdisabled": true,
|
||||||
|
}
|
||||||
|
|
||||||
|
ta, err = checkOptions(options)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if ta.autoRedirect != true {
|
||||||
|
t.Fatal("autoredirect should be true")
|
||||||
|
}
|
||||||
|
if ta.autoRedirectPath != "/auth/token" {
|
||||||
|
t.Fatal("autoredirectpath should be /auth/token")
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue