0
Fork 0
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:
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": {
"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.

View file

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

View file

@ -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)),

View file

@ -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

View file

@ -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()