Skip to content

Commit

Permalink
feat(ldap): add option to load ldap from file
Browse files Browse the repository at this point in the history
Signed-off-by: Laurentiu Niculae <[email protected]>
  • Loading branch information
laurentiuNiculae committed Sep 15, 2023
1 parent aae8b7b commit 1742024
Show file tree
Hide file tree
Showing 4 changed files with 283 additions and 0 deletions.
6 changes: 6 additions & 0 deletions pkg/api/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,13 @@ type SchedulerConfig struct {
NumWorkers int
}

type LDAPCredentials struct {
BindDN string
BindPassword string
}

type LDAPConfig struct {
CredentialsFile string
Port int
Insecure bool
StartTLS bool // if !Insecure, then StartTLS or LDAPs
Expand Down
184 changes: 184 additions & 0 deletions pkg/api/controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ import (
"zotregistry.io/zot/pkg/api/config"
"zotregistry.io/zot/pkg/api/constants"
apiErr "zotregistry.io/zot/pkg/api/errors"
"zotregistry.io/zot/pkg/cli"
"zotregistry.io/zot/pkg/common"
extconf "zotregistry.io/zot/pkg/extensions/config"
"zotregistry.io/zot/pkg/log"
Expand Down Expand Up @@ -2053,6 +2054,93 @@ func TestBasicAuthWithLDAP(t *testing.T) {
})
}

func TestBasicAuthWithLDAPFromFile(t *testing.T) {
Convey("Make a new controller", t, func() {
l := newTestLDAPServer()
port := test.GetFreePort()
ldapPort, err := strconv.Atoi(port)
So(err, ShouldBeNil)
l.Start(ldapPort)
defer l.Stop()

port = test.GetFreePort()
baseURL := test.GetBaseURL(port)
tempDir := t.TempDir()

ldapConfigContent := fmt.Sprintf(`
{
"BindDN": "%v",
"BindPassword": "%v"
}`, LDAPBindDN, LDAPBindPassword)

ldapConfigPath := filepath.Join(tempDir, "ldap.json")

err = os.WriteFile(ldapConfigPath, []byte(ldapConfigContent), 0o600)
So(err, ShouldBeNil)

configStr := fmt.Sprintf(`
{
"Storage": {
"RootDirectory": "%s"
},
"HTTP": {
"Address": "%s",
"Port": "%s",
"Auth": {
"LDAP": {
"CredentialsFile": "%s",
"BaseDN": "%v",
"UserAttribute": "uid",
"UserGroupAttribute": "memberOf",
"Insecure": true,
"Address": "%v",
"Port": %v
}
}
}
}`, tempDir, "127.0.0.1", port, ldapConfigPath, LDAPBaseDN, LDAPAddress, ldapPort)

configPath := filepath.Join(tempDir, "config.json")

err = os.WriteFile(configPath, []byte(configStr), 0o0600)
So(err, ShouldBeNil)

server := cli.NewServerRootCmd()
server.SetArgs([]string{"serve", configPath})
go func() {
err := server.Execute()
if err != nil {
panic(err)
}
}()

test.WaitTillServerReady(baseURL)

// without creds, should get access error
resp, err := resty.R().Get(baseURL + "/v2/")
So(err, ShouldBeNil)
So(resp, ShouldNotBeNil)
So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized)
var e apiErr.Error
err = json.Unmarshal(resp.Body(), &e)
So(err, ShouldBeNil)

// with creds, should get expected status code
resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL)
So(resp, ShouldNotBeNil)
So(resp.StatusCode(), ShouldEqual, http.StatusNotFound)

resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + "/v2/")
So(resp, ShouldNotBeNil)
So(resp.StatusCode(), ShouldEqual, http.StatusOK)

// missing password
resp, _ = resty.R().SetBasicAuth(username, "").Get(baseURL + "/v2/")
So(resp, ShouldNotBeNil)
So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized)
})
}

func TestGroupsPermissionsForLDAP(t *testing.T) {
Convey("Make a new controller", t, func() {
l := newTestLDAPServer()
Expand Down Expand Up @@ -2119,6 +2207,102 @@ func TestGroupsPermissionsForLDAP(t *testing.T) {
})
}

func TestLDAPConfigFromFile(t *testing.T) {
Convey("Make a new controller", t, func() {
l := newTestLDAPServer()
port := test.GetFreePort()
ldapPort, err := strconv.Atoi(port)
So(err, ShouldBeNil)
l.Start(ldapPort)
defer l.Stop()

port = test.GetFreePort()
baseURL := test.GetBaseURL(port)
tempDir := t.TempDir()

ldapConfigContent := fmt.Sprintf(`
{
"BindDN": "%v",
"BindPassword": "%v"
}`, LDAPBindDN, LDAPBindPassword)

ldapConfigPath := filepath.Join(tempDir, "ldap.json")

err = os.WriteFile(ldapConfigPath, []byte(ldapConfigContent), 0o600)
So(err, ShouldBeNil)

configStr := fmt.Sprintf(`
{
"Storage": {
"RootDirectory": "%s"
},
"HTTP": {
"Address": "%s",
"Port": "%s",
"Auth": {
"LDAP": {
"CredentialsFile": "%s",
"BaseDN": "%v",
"UserAttribute": "uid",
"UserGroupAttribute": "memberOf",
"Insecure": true,
"Address": "%v",
"Port": %v
}
},
"AccessControl": {
"repositories": {
"test": {
"Policies": [
{
"Users": null,
"Actions": [
"read",
"create"
],
"Groups": [
"test"
]
}
]
}
},
"Groups": {
"test": {
"Users": [
"test"
]
}
}
}
}
}`, tempDir, "127.0.0.1", port, ldapConfigPath, LDAPBaseDN, LDAPAddress, ldapPort)

configPath := filepath.Join(tempDir, "config.json")

err = os.WriteFile(configPath, []byte(configStr), 0o0600)
So(err, ShouldBeNil)

server := cli.NewServerRootCmd()
server.SetArgs([]string{"serve", configPath})
go func() {
err := server.Execute()
if err != nil {
panic(err)
}
}()

test.WaitTillServerReady(baseURL)

img := test.CreateDefaultImage()

err = test.UploadImageWithBasicAuth(
img, baseURL, repo, img.DigestStr(),
username, passphrase)
So(err, ShouldBeNil)
})
}

func TestLDAPFailures(t *testing.T) {
Convey("Make a LDAP conn", t, func() {
l := newTestLDAPServer()
Expand Down
41 changes: 41 additions & 0 deletions pkg/cli/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package cli

import (
"context"
"encoding/json"
"fmt"
"net"
"net/http"
Expand Down Expand Up @@ -757,6 +758,10 @@ func LoadConfiguration(config *config.Config, configPath string) error {
return zerr.ErrBadConfig
}

if err := updateLDAPConfig(config); err != nil {
return err
}

// defaults
applyDefaultValues(config, viperInstance, log)

Expand All @@ -771,6 +776,42 @@ func LoadConfiguration(config *config.Config, configPath string) error {
return nil
}

func updateLDAPConfig(conf *config.Config) error {
if conf.HTTP.Auth == nil || conf.HTTP.Auth.LDAP == nil {
return nil
}

if conf.HTTP.Auth.LDAP.CredentialsFile == "" {
return nil
}

newLDAPCredentials, err := readLDAPCredentials(conf.HTTP.Auth.LDAP.CredentialsFile)
if err != nil {
return err
}

conf.HTTP.Auth.LDAP.BindDN = newLDAPCredentials.BindDN
conf.HTTP.Auth.LDAP.BindPassword = newLDAPCredentials.BindPassword

return nil
}

func readLDAPCredentials(ldapConfigPath string) (config.LDAPCredentials, error) {
ldapConfigBlob, err := os.ReadFile(ldapConfigPath)
if err != nil {
return config.LDAPCredentials{}, err
}

var ldapCredentials config.LDAPCredentials

err = json.Unmarshal(ldapConfigBlob, &ldapCredentials)
if err != nil {
return config.LDAPCredentials{}, err
}

return ldapCredentials, nil
}

func authzContainsOnlyAnonymousPolicy(cfg *config.Config) bool {
adminPolicy := cfg.HTTP.AccessControl.AdminPolicy
anonymousPolicyPresent := false
Expand Down
52 changes: 52 additions & 0 deletions pkg/cli/root_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"os"
"path"
"path/filepath"
"testing"
"time"

Expand Down Expand Up @@ -1714,6 +1715,57 @@ func TestScrub(t *testing.T) {
})
}

func TestUpdateLDAPConfig(t *testing.T) {
Convey("updateLDAPConfig", t, func() {
tempDir := t.TempDir()

ldapConfigContent := "bad-json"

ldapConfigPath := filepath.Join(tempDir, "ldap.json")

err := os.WriteFile(ldapConfigPath, []byte(ldapConfigContent), 0o000)
So(err, ShouldBeNil)

configStr := fmt.Sprintf(`
{
"Storage": {
"RootDirectory": "%s"
},
"HTTP": {
"Address": "%s",
"Port": "%s",
"Auth": {
"LDAP": {
"CredentialsFile": "%s",
"BaseDN": "%v",
"UserAttribute": "uid",
"UserGroupAttribute": "memberOf",
"Insecure": true,
"Address": "%v",
"Port": %v
}
}
}
}`, tempDir, "127.0.0.1", "8000", ldapConfigPath, "LDAPBaseDN", "LDAPAddress", 1000)

configPath := filepath.Join(tempDir, "config.json")

err = os.WriteFile(configPath, []byte(configStr), 0o0600)
So(err, ShouldBeNil)

server := cli.NewServerRootCmd()
server.SetArgs([]string{"serve", configPath})
So(func() { err = server.Execute() }, ShouldPanic)

err = os.Chmod(ldapConfigPath, 0o600)
So(err, ShouldBeNil)

server = cli.NewServerRootCmd()
server.SetArgs([]string{"serve", configPath})
So(func() { err = server.Execute() }, ShouldPanic)
})
}

// run cli and return output.
func runCLIWithConfig(tempDir string, config string) (string, error) {
port := GetFreePort()
Expand Down

0 comments on commit 1742024

Please sign in to comment.