0
Fork 0
mirror of https://github.com/project-zot/zot.git synced 2025-01-06 22:40:28 -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:
peusebiu 2023-08-09 19:11:21 +03:00 committed by GitHub
parent ed90e3bd24
commit 4d125d55ed
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 320 additions and 259 deletions

View file

@ -181,6 +181,8 @@ zot can be configured to use the above providers with:
``` ```
{ {
"http": { "http": {
"address": "127.0.0.1",
"port": "8080",
"auth": { "auth": {
"openid": { "openid": {
"providers": { "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 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 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 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. 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 ### 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. 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.

View file

@ -7,6 +7,7 @@
"http": { "http": {
"address": "127.0.0.1", "address": "127.0.0.1",
"port": "8080", "port": "8080",
"externalUrl": "http://127.0.0.1:8080",
"realm": "zot", "realm": "zot",
"auth": { "auth": {
"htpasswd": { "htpasswd": {

View file

@ -586,11 +586,6 @@ func getRelyingPartyArgs(cfg *config.Config, provider string) (
panic(zerr.ErrOpenIDProviderDoesNotExist) panic(zerr.ErrOpenIDProviderDoesNotExist)
} }
scheme := "http"
if cfg.HTTP.TLS != nil {
scheme = "https"
}
clientID := cfg.HTTP.Auth.OpenID.Providers[provider].ClientID clientID := cfg.HTTP.Auth.OpenID.Providers[provider].ClientID
clientSecret := cfg.HTTP.Auth.OpenID.Providers[provider].ClientSecret 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 issuer := cfg.HTTP.Auth.OpenID.Providers[provider].Issuer
keyPath := cfg.HTTP.Auth.OpenID.Providers[provider].KeyPath keyPath := cfg.HTTP.Auth.OpenID.Providers[provider].KeyPath
baseURL := net.JoinHostPort(cfg.HTTP.Address, port) 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{ options := []rp.Option{
rp.WithVerifierOpts(rp.WithIssuedAtOffset(issuedAtOffset)), rp.WithVerifierOpts(rp.WithIssuedAtOffset(issuedAtOffset)),

View file

@ -84,6 +84,7 @@ type RatelimitConfig struct {
//nolint:maligned //nolint:maligned
type HTTPConfig struct { type HTTPConfig struct {
Address string Address string
ExternalURL string `mapstructure:",omitempty"`
Port string Port string
AllowOrigin string // comma separated AllowOrigin string // comma separated
TLS *TLSConfig TLS *TLSConfig

View file

@ -2595,6 +2595,23 @@ func TestOpenIDMiddleware(t *testing.T) {
conf := config.New() conf := config.New()
conf.HTTP.Port = port 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 // need a username different than ldap one, to test both logic
content := fmt.Sprintf("%s:$2y$05$hlbSXDp6hzDLu6VwACS39ORvVRpr3OMR4RlJ31jtlaOEGnPjKZI1m\n", htpasswdUsername) content := fmt.Sprintf("%s:$2y$05$hlbSXDp6hzDLu6VwACS39ORvVRpr3OMR4RlJ31jtlaOEGnPjKZI1m\n", htpasswdUsername)
htpasswdPath := test.MakeHtpasswdFileFromString(content) htpasswdPath := test.MakeHtpasswdFileFromString(content)
@ -2674,10 +2691,14 @@ func TestOpenIDMiddleware(t *testing.T) {
} }
ctlr := api.NewController(conf) ctlr := api.NewController(conf)
for _, testcase := range testCases {
t.Run(testcase.testCaseName, func(t *testing.T) {
dir := t.TempDir() dir := t.TempDir()
ctlr.Config.Storage.RootDirectory = dir ctlr.Config.Storage.RootDirectory = dir
ctlr.Config.HTTP.ExternalURL = testcase.externalURL
ctlr.Config.HTTP.Address = testcase.address
cm := test.NewControllerManager(ctlr) cm := test.NewControllerManager(ctlr)
cm.StartServer() cm.StartServer()
@ -2907,6 +2928,8 @@ func TestOpenIDMiddleware(t *testing.T) {
So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized) So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized)
}) })
}) })
})
}
} }
func TestIsOpenIDEnabled(t *testing.T) { func TestIsOpenIDEnabled(t *testing.T) {
@ -2931,38 +2954,38 @@ func TestIsOpenIDEnabled(t *testing.T) {
rootDir := t.TempDir() rootDir := t.TempDir()
// Convey("Only OAuth2 provided", func() { Convey("Only OAuth2 provided", func() {
// mockOIDCConfig := mockOIDCServer.Config() mockOIDCConfig := mockOIDCServer.Config()
// conf.HTTP.Auth = &config.AuthConfig{ conf.HTTP.Auth = &config.AuthConfig{
// OpenID: &config.OpenIDConfig{ OpenID: &config.OpenIDConfig{
// Providers: map[string]config.OpenIDProviderConfig{ Providers: map[string]config.OpenIDProviderConfig{
// "github": { "github": {
// ClientID: mockOIDCConfig.ClientID, ClientID: mockOIDCConfig.ClientID,
// ClientSecret: mockOIDCConfig.ClientSecret, ClientSecret: mockOIDCConfig.ClientSecret,
// KeyPath: "", KeyPath: "",
// Issuer: mockOIDCConfig.Issuer, Issuer: mockOIDCConfig.Issuer,
// Scopes: []string{"email", "groups"}, 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() cm.StartServer()
// defer cm.StopServer() defer cm.StopServer()
// test.WaitTillServerReady(baseURL) test.WaitTillServerReady(baseURL)
// resp, err := resty.R(). resp, err := resty.R().
// Get(baseURL + "/v2/") Get(baseURL + "/v2/")
// So(err, ShouldBeNil) So(err, ShouldBeNil)
// So(resp, ShouldNotBeNil) So(resp, ShouldNotBeNil)
// So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized) So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized)
// }) })
Convey("Unsupported provider", func() { Convey("Unsupported provider", func() {
mockOIDCConfig := mockOIDCServer.Config() mockOIDCConfig := mockOIDCServer.Config()