mirror of
https://github.com/project-zot/zot.git
synced 2024-12-16 21:56:37 -05:00
fix(authn): handle the case where zot with openid runs behind a proxy (#1675)
added a new config option under 'http' called externalURL which is used by openid/oauth2 clients to redirect back to zot Signed-off-by: Petu Eusebiu <peusebiu@cisco.com>
This commit is contained in:
parent
ed90e3bd24
commit
4d125d55ed
5 changed files with 320 additions and 259 deletions
|
@ -181,6 +181,8 @@ zot can be configured to use the above providers with:
|
|||
```
|
||||
{
|
||||
"http": {
|
||||
"address": "127.0.0.1",
|
||||
"port": "8080",
|
||||
"auth": {
|
||||
"openid": {
|
||||
"providers": {
|
||||
|
@ -207,7 +209,7 @@ zot can be configured to use the above providers with:
|
|||
}
|
||||
```
|
||||
|
||||
The login with either provider use http://127.0.0.1:8080/auth/login?provider=\<provider\>&callback_ui=http://127.0.0.1:8080/home
|
||||
To login with either provider use http://127.0.0.1:8080/auth/login?provider=\<provider\>&callback_ui=http://127.0.0.1:8080/home
|
||||
for example to login with github use http://127.0.0.1:8080/auth/login?provider=github&callback_ui=http://127.0.0.1:8080/home
|
||||
|
||||
callback_ui query parameter is used by zot to redirect to UI after a successful openid/oauth2 authentication
|
||||
|
@ -258,6 +260,30 @@ images to/from zot.
|
|||
Given this limitation, if openif authentication is enabled in the configuration, API keys are also enabled
|
||||
implicitly, as a viable alternative authentication method for pushing and pulling container images.
|
||||
|
||||
### OpenID/OAuth2 social login behind a proxy/load balancer
|
||||
|
||||
In the case of running zot with openid enabled behind a proxy/load balancer http.externalUrl should be provided.
|
||||
|
||||
```
|
||||
"http": {
|
||||
"address": "0.0.0.0",
|
||||
"port": "8080",
|
||||
"externalUrl: "https://zot.example.com",
|
||||
"auth": {
|
||||
"openid": {
|
||||
"providers": {
|
||||
"github": {
|
||||
"clientid": <client_id>,
|
||||
"clientsecret": <client_secret>,
|
||||
"scopes": ["read:org", "user", "repo"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
This config value will be used by oauth2/openid clients to redirect back to zot.
|
||||
|
||||
### Session based login
|
||||
|
||||
Whenever a user logs in zot using any of the auth options available(basic auth/openid) zot will set a 'session' cookie on its response.
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
"http": {
|
||||
"address": "127.0.0.1",
|
||||
"port": "8080",
|
||||
"externalUrl": "http://127.0.0.1:8080",
|
||||
"realm": "zot",
|
||||
"auth": {
|
||||
"htpasswd": {
|
||||
|
|
|
@ -586,11 +586,6 @@ func getRelyingPartyArgs(cfg *config.Config, provider string) (
|
|||
panic(zerr.ErrOpenIDProviderDoesNotExist)
|
||||
}
|
||||
|
||||
scheme := "http"
|
||||
if cfg.HTTP.TLS != nil {
|
||||
scheme = "https"
|
||||
}
|
||||
|
||||
clientID := cfg.HTTP.Auth.OpenID.Providers[provider].ClientID
|
||||
clientSecret := cfg.HTTP.Auth.OpenID.Providers[provider].ClientSecret
|
||||
|
||||
|
@ -604,7 +599,22 @@ func getRelyingPartyArgs(cfg *config.Config, provider string) (
|
|||
issuer := cfg.HTTP.Auth.OpenID.Providers[provider].Issuer
|
||||
keyPath := cfg.HTTP.Auth.OpenID.Providers[provider].KeyPath
|
||||
baseURL := net.JoinHostPort(cfg.HTTP.Address, port)
|
||||
redirectURI := fmt.Sprintf("%s://%s%s", scheme, baseURL, constants.CallbackBasePath+fmt.Sprintf("/%s", provider))
|
||||
|
||||
callback := constants.CallbackBasePath + fmt.Sprintf("/%s", provider)
|
||||
|
||||
var redirectURI string
|
||||
|
||||
if cfg.HTTP.ExternalURL != "" {
|
||||
externalURL := strings.TrimSuffix(cfg.HTTP.ExternalURL, "/")
|
||||
redirectURI = fmt.Sprintf("%s%s", externalURL, callback)
|
||||
} else {
|
||||
scheme := "http"
|
||||
if cfg.HTTP.TLS != nil {
|
||||
scheme = "https"
|
||||
}
|
||||
|
||||
redirectURI = fmt.Sprintf("%s://%s%s", scheme, baseURL, callback)
|
||||
}
|
||||
|
||||
options := []rp.Option{
|
||||
rp.WithVerifierOpts(rp.WithIssuedAtOffset(issuedAtOffset)),
|
||||
|
|
|
@ -84,6 +84,7 @@ type RatelimitConfig struct {
|
|||
//nolint:maligned
|
||||
type HTTPConfig struct {
|
||||
Address string
|
||||
ExternalURL string `mapstructure:",omitempty"`
|
||||
Port string
|
||||
AllowOrigin string // comma separated
|
||||
TLS *TLSConfig
|
||||
|
|
|
@ -2595,6 +2595,23 @@ func TestOpenIDMiddleware(t *testing.T) {
|
|||
conf := config.New()
|
||||
conf.HTTP.Port = port
|
||||
|
||||
testCases := []struct {
|
||||
testCaseName string
|
||||
address string
|
||||
externalURL string
|
||||
}{
|
||||
{
|
||||
address: "0.0.0.0",
|
||||
externalURL: fmt.Sprintf("http://%s", net.JoinHostPort(conf.HTTP.Address, conf.HTTP.Port)),
|
||||
testCaseName: "with ExternalURL provided in config",
|
||||
},
|
||||
{
|
||||
address: "127.0.0.1",
|
||||
externalURL: "",
|
||||
testCaseName: "without ExternalURL provided in config",
|
||||
},
|
||||
}
|
||||
|
||||
// need a username different than ldap one, to test both logic
|
||||
content := fmt.Sprintf("%s:$2y$05$hlbSXDp6hzDLu6VwACS39ORvVRpr3OMR4RlJ31jtlaOEGnPjKZI1m\n", htpasswdUsername)
|
||||
htpasswdPath := test.MakeHtpasswdFileFromString(content)
|
||||
|
@ -2674,10 +2691,14 @@ func TestOpenIDMiddleware(t *testing.T) {
|
|||
}
|
||||
|
||||
ctlr := api.NewController(conf)
|
||||
|
||||
for _, testcase := range testCases {
|
||||
t.Run(testcase.testCaseName, func(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
|
||||
ctlr.Config.Storage.RootDirectory = dir
|
||||
|
||||
ctlr.Config.HTTP.ExternalURL = testcase.externalURL
|
||||
ctlr.Config.HTTP.Address = testcase.address
|
||||
cm := test.NewControllerManager(ctlr)
|
||||
|
||||
cm.StartServer()
|
||||
|
@ -2907,6 +2928,8 @@ func TestOpenIDMiddleware(t *testing.T) {
|
|||
So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsOpenIDEnabled(t *testing.T) {
|
||||
|
@ -2931,38 +2954,38 @@ func TestIsOpenIDEnabled(t *testing.T) {
|
|||
|
||||
rootDir := t.TempDir()
|
||||
|
||||
// Convey("Only OAuth2 provided", func() {
|
||||
// mockOIDCConfig := mockOIDCServer.Config()
|
||||
// conf.HTTP.Auth = &config.AuthConfig{
|
||||
// OpenID: &config.OpenIDConfig{
|
||||
// Providers: map[string]config.OpenIDProviderConfig{
|
||||
// "github": {
|
||||
// ClientID: mockOIDCConfig.ClientID,
|
||||
// ClientSecret: mockOIDCConfig.ClientSecret,
|
||||
// KeyPath: "",
|
||||
// Issuer: mockOIDCConfig.Issuer,
|
||||
// Scopes: []string{"email", "groups"},
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// }
|
||||
Convey("Only OAuth2 provided", func() {
|
||||
mockOIDCConfig := mockOIDCServer.Config()
|
||||
conf.HTTP.Auth = &config.AuthConfig{
|
||||
OpenID: &config.OpenIDConfig{
|
||||
Providers: map[string]config.OpenIDProviderConfig{
|
||||
"github": {
|
||||
ClientID: mockOIDCConfig.ClientID,
|
||||
ClientSecret: mockOIDCConfig.ClientSecret,
|
||||
KeyPath: "",
|
||||
Issuer: mockOIDCConfig.Issuer,
|
||||
Scopes: []string{"email", "groups"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// ctlr := api.NewController(conf)
|
||||
ctlr := api.NewController(conf)
|
||||
|
||||
// ctlr.Config.Storage.RootDirectory = rootDir
|
||||
ctlr.Config.Storage.RootDirectory = rootDir
|
||||
|
||||
// cm := test.NewControllerManager(ctlr)
|
||||
cm := test.NewControllerManager(ctlr)
|
||||
|
||||
// cm.StartServer()
|
||||
// defer cm.StopServer()
|
||||
// test.WaitTillServerReady(baseURL)
|
||||
cm.StartServer()
|
||||
defer cm.StopServer()
|
||||
test.WaitTillServerReady(baseURL)
|
||||
|
||||
// resp, err := resty.R().
|
||||
// Get(baseURL + "/v2/")
|
||||
// So(err, ShouldBeNil)
|
||||
// So(resp, ShouldNotBeNil)
|
||||
// So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized)
|
||||
// })
|
||||
resp, err := resty.R().
|
||||
Get(baseURL + "/v2/")
|
||||
So(err, ShouldBeNil)
|
||||
So(resp, ShouldNotBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized)
|
||||
})
|
||||
|
||||
Convey("Unsupported provider", func() {
|
||||
mockOIDCConfig := mockOIDCServer.Config()
|
||||
|
|
Loading…
Reference in a new issue