add new ldap auth and import user feature

This commit is contained in:
yhua 2017-02-24 18:30:57 +08:00
parent 01e105090e
commit c48d908515
13 changed files with 1168 additions and 446 deletions

View File

@ -83,7 +83,10 @@ script:
- export MYSQL_HOST=$IP
- export REGISTRY_URL=$IP:5000
- echo $REGISTRY_URL
- ./tests/pushimage.sh
- ./tests/pushimage.sh
- cd tests
- sudo ./ldapprepare.sh
- cd ..
- ./tests/coverage4gotest.sh
- goveralls -coverprofile=profile.cov -service=travis-ci

View File

@ -1352,12 +1352,12 @@ paths:
post:
summary: Ping available ldap service.
description: |
This endpoint ping the available ldap service for test related configuration parameters.
This endpoint ping the available ldap service for test related configuration parameters.
parameters:
- name: ldapconf
in: body
description: ldap configuration.
required: true
description: ldap configuration. support input ldap service configuration. If it's a empty request, will load current configuration from the system.
required: false
schema:
$ref: '#/definitions/LdapConf'
tags:
@ -1365,12 +1365,76 @@ paths:
responses:
200:
description: Ping ldap service successfully.
401:
description: Only admin has this authority.
403:
400:
description: Inviald ldap configuration parameters.
401:
description: User need to login first.
403:
description: Only admin has this authority.
500:
description: Unexpected internal errors.
description: Unexpected internal errors.
/ldap/users/search:
post:
summary: Search available ldap users.
description: |
This endpoint searches the available ldap users based on related configuration parameters. Support searched by input ladp configuration, load configuration from the system and specific filter.
parameters:
- name: username
in: query
type: string
required: false
description: Registered user ID
- name: ldap_conf
in: body
description: ldap search configuration. ldapconf field can input ldap service configuration. If this item are blank, will load default configuration will load current configuration from the system.
required: false
schema:
$ref: '#/definitions/LdapConf'
tags:
- Products
responses:
200:
description: Search ldap users successfully.
schema:
type: array
items:
$ref: '#/definitions/LdapUsers'
400:
description: Inviald ldap configuration parameters.
401:
description: User need to login first.
403:
description: Only admin has this authority.
500:
description: Unexpected internal errors.
/ldap/users/import:
post:
summary: Import selected available ldap users.
description: |
This endpoint adds the selected available ldap users to harbor based on related configuration parameters from the system. System will try to guess the user email address and realname, add to harbor user information.
If have errors when import user, will return the list of importing failed uid and the failed reason.
parameters:
- name: uid_list
in: body
description: The uid listed for importing. This list will check users validity of ldap service based on configuration from the system.
required: true
schema:
$ref: '#/definitions/LdapImportUsers'
tags:
- Products
responses:
200:
description: Add ldap users successfully.
401:
description: User need to login first.
403:
description: Only admin has this authority.
500:
description: Failed import some users.
schema:
type: array
items:
$ref: '#/definitions/LdapFailedImportUsers'
/configurations:
get:
summary: Get system configurations.
@ -1881,7 +1945,7 @@ definitions:
description: The serach filter of ldap service.
ldap_uid:
type: string
description: The serach uid of ldap service.
description: The serach uid from ldap service attributes.
ldap_scope:
type: integer
format: int64
@ -1890,3 +1954,32 @@ definitions:
type: integer
format: int64
description: The connect timeout of ldap service(second).
LdapUsers:
type: object
properties:
ldap_username:
type: string
description: search ldap user name based on ldapconf.
ldap_realname:
type: string
description: system will try to guess the user realname form "uid" or "cn" attribute.
ldap_email:
type: string
description: system will try to guess the user email address form "mail" or "email" attribute.
LdapImportUsers:
type: object
properties:
ldap_uid_list:
type: array
description: selected uid list
items:
type: string
LdapFailedImportUsers:
type: object
properties:
ldap_uid:
type: string
description: the uid can't add to system.
error:
type: string
description: fail reason.

View File

@ -15,14 +15,32 @@
package models
// LdapConf holds information about repository that accessed most
// LdapConf holds information about ldap configuration
type LdapConf struct {
LdapURL string `json:"ldap_url"`
LdapSearchDn string `json:"ldap_search_dn"`
LdapSearchPassword string `json:"ldap_search_password"`
LdapBaseDn string `json:"ldap_base_dn"`
LdapFilter string `json:"ldap_filter"`
LdapUID string `json:"ldap_uid"`
LdapScope int `json:"ldap_scope"`
LdapURL string `json:"ldap_url"`
LdapSearchDn string `json:"ldap_search_dn"`
LdapSearchPassword string `json:"ldap_search_password"`
LdapBaseDn string `json:"ldap_base_dn"`
LdapFilter string `json:"ldap_filter"`
LdapUID string `json:"ldap_uid"`
LdapScope int `json:"ldap_scope"`
LdapConnectionTimeout int `json:"ldap_connection_timeout"`
}
// LdapUser ...
type LdapUser struct {
Username string `json:"ldap_username"`
Email string `json:"ldap_email"`
Realname string `json:"ldap_realname"`
}
//LdapImportUser ...
type LdapImportUser struct {
LdapUIDList []string `json:"ldap_uid_list"`
}
// LdapFailedImportUser ...
type LdapFailedImportUser struct {
UID string `json:"uid"`
Error string `json:"err_msg"`
}

View File

@ -0,0 +1,382 @@
/*
Copyright (c) 2016 VMware, Inc. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package ldap
import (
"fmt"
"net/url"
"strconv"
"strings"
"time"
"crypto/tls"
"github.com/vmware/harbor/src/common/dao"
"github.com/vmware/harbor/src/common/models"
"github.com/vmware/harbor/src/common/utils/log"
"github.com/vmware/harbor/src/ui/config"
goldap "gopkg.in/ldap.v2"
)
var attributes = []string{"uid", "cn", "mail", "email"}
// GetSystemLdapConf ...
func GetSystemLdapConf() (models.LdapConf, error) {
var err error
var ldapConfs models.LdapConf
var authMode string
authMode, err = config.AuthMode()
if err != nil {
log.Errorf("can't load auth mode from system, error: %v", err)
return ldapConfs, err
}
if authMode != "ldap_auth" {
return ldapConfs, fmt.Errorf("system auth_mode isn't ldap_auth, please check configuration")
}
ldap, err := config.LDAP()
ldapConfs.LdapURL = ldap.URL
ldapConfs.LdapSearchDn = ldap.SearchDN
ldapConfs.LdapSearchPassword = ldap.SearchPassword
ldapConfs.LdapBaseDn = ldap.BaseDN
ldapConfs.LdapFilter = ldap.Filter
ldapConfs.LdapUID = ldap.UID
ldapConfs.LdapScope = ldap.Scope
ldapConfs.LdapConnectionTimeout = ldap.Timeout
// ldapConfs = config.LDAP().URL
// ldapConfs.LdapSearchDn = config.LDAP().SearchDn
// ldapConfs.LdapSearchPassword = config.LDAP().SearchPwd
// ldapConfs.LdapBaseDn = config.LDAP().BaseDn
// ldapConfs.LdapFilter = config.LDAP().Filter
// ldapConfs.LdapUID = config.LDAP().UID
// ldapConfs.LdapScope, err = strconv.Atoi(config.LDAP().Scope)
// if err != nil {
// log.Errorf("invalid LdapScope format from system, error: %v", err)
// return ldapConfs, err
// }
// ldapConfs.LdapConnectionTimeout, err = strconv.Atoi(config.LDAP().ConnectTimeout)
// if err != nil {
// log.Errorf("invalid LdapConnectionTimeout format from system, error: %v", err)
// return ldapConfs, err
// }
return ldapConfs, nil
}
// ValidateLdapConf ...
func ValidateLdapConf(ldapConfs models.LdapConf) (models.LdapConf, error) {
var err error
if ldapConfs.LdapURL == "" {
return ldapConfs, fmt.Errorf("can not get any available LDAP_URL")
}
ldapConfs.LdapURL, err = formatLdapURL(ldapConfs.LdapURL)
if err != nil {
log.Errorf("invalid LdapURL format, error: %v", err)
return ldapConfs, err
}
// Compatible with legacy codes
// in previous harbor.cfg:
// the scope to search for users, 1-LDAP_SCOPE_BASE, 2-LDAP_SCOPE_ONELEVEL, 3-LDAP_SCOPE_SUBTREE
switch ldapConfs.LdapScope {
case 1:
ldapConfs.LdapScope = goldap.ScopeBaseObject
case 2:
ldapConfs.LdapScope = goldap.ScopeSingleLevel
case 3:
ldapConfs.LdapScope = goldap.ScopeWholeSubtree
default:
return ldapConfs, fmt.Errorf("invalid ldap search scope")
}
// value := reflect.ValueOf(ldapConfs)
// lType := reflect.TypeOf(ldapConfs)
// for i := 0; i < value.NumField(); i++ {
// fmt.Printf("Field %d: %v %v\n", i, value.Field(i), lType.Field(i).Name)
// }
return ldapConfs, nil
}
// MakeFilter ...
func MakeFilter(username string, ldapFilter string, ldapUID string) string {
var filterTag string
if username == "" {
filterTag = "*"
} else {
filterTag = username
}
if ldapFilter == "" {
ldapFilter = "(" + ldapUID + "=" + filterTag + ")"
} else {
if !strings.Contains(ldapFilter, ldapUID+"=") {
ldapFilter = "(&" + ldapFilter + "(" + ldapUID + "=" + filterTag + "))"
} else {
ldapFilter = strings.Replace(ldapFilter, ldapUID+"=*", ldapUID+"="+filterTag, -1)
}
}
log.Debug("one or more ldapFilter: ", ldapFilter)
return ldapFilter
}
// ConnectTest ...
func ConnectTest(ldapConfs models.LdapConf) error {
var ldapConn *goldap.Conn
var err error
ldapConn, err = dialLDAP(ldapConfs, ldapConn)
if err != nil {
return err
}
defer ldapConn.Close()
if ldapConfs.LdapSearchDn != "" {
err = bindLDAPSearchDN(ldapConfs, ldapConn)
if err != nil {
return err
}
}
return nil
}
// SearchUser ...
func SearchUser(ldapConfs models.LdapConf) ([]models.LdapUser, error) {
var ldapUsers []models.LdapUser
var ldapConn *goldap.Conn
var err error
ldapConn, err = dialLDAP(ldapConfs, ldapConn)
if err != nil {
return nil, err
}
defer ldapConn.Close()
if ldapConfs.LdapSearchDn != "" {
err = bindLDAPSearchDN(ldapConfs, ldapConn)
if err != nil {
return nil, err
}
}
if ldapConfs.LdapBaseDn == "" {
return nil, fmt.Errorf("can not get any available LDAP_BASE_DN")
}
result, err := searchLDAP(ldapConfs, ldapConn)
if err != nil {
return nil, err
}
for _, ldapEntry := range result.Entries {
var u models.LdapUser
for _, attr := range ldapEntry.Attributes {
val := attr.Values[0]
switch attr.Name {
case ldapConfs.LdapUID:
u.Username = val
case "uid":
u.Realname = val
case "cn":
u.Realname = val
case "mail":
u.Email = val
case "email":
u.Email = val
}
}
ldapUsers = append(ldapUsers, u)
}
return ldapUsers, nil
}
func formatLdapURL(ldapURL string) (string, error) {
var protocol, hostport string
var err error
_, err = url.Parse(ldapURL)
if err != nil {
return "", fmt.Errorf("parse Ldap Host ERR: %s", err)
}
if strings.Contains(ldapURL, "://") {
splitLdapURL := strings.Split(ldapURL, "://")
protocol, hostport = splitLdapURL[0], splitLdapURL[1]
if !((protocol == "ldap") || (protocol == "ldaps")) {
return "", fmt.Errorf("unknown ldap protocl")
}
} else {
hostport = ldapURL
protocol = "ldap"
}
if strings.Contains(hostport, ":") {
splitHostPort := strings.Split(hostport, ":")
port, error := strconv.Atoi(splitHostPort[1])
if error != nil {
return "", fmt.Errorf("illegal url port")
}
if port == 636 {
protocol = "ldaps"
}
} else {
switch protocol {
case "ldap":
hostport = hostport + ":389"
case "ldaps":
hostport = hostport + ":636"
}
}
fLdapURL := protocol + "://" + hostport
return fLdapURL, nil
}
// ImportUser ...
func ImportUser(user models.LdapUser) (int64, error) {
var u models.User
u.Username = user.Username
u.Email = user.Email
u.Realname = user.Realname
log.Debug("username:", u.Username, ",email:", u.Email)
exist, err := dao.UserExists(u, "username")
if err != nil {
log.Errorf("system checking user %s failed, error: %v", user.Username, err)
return 0, fmt.Errorf("internal_error")
}
if exist {
return 0, fmt.Errorf("duplicate_username")
}
exist, err = dao.UserExists(u, "email")
if err != nil {
log.Errorf("system checking %s mailbox failed, error: %v", user.Username, err)
return 0, fmt.Errorf("internal_error")
}
if exist {
return 0, fmt.Errorf("duplicate_mailbox")
}
u.Password = "12345678AbC"
u.Comment = "registered from LDAP."
if u.Email == "" {
u.Email = u.Username + "@placeholder.com"
}
UserID, err := dao.Register(u)
if err != nil {
log.Errorf("system register user %s failed, error: %v", user.Username, err)
return 0, fmt.Errorf("registe_user_error")
}
return UserID, nil
}
func dialLDAP(ldapConfs models.LdapConf, ldap *goldap.Conn) (*goldap.Conn, error) {
var err error
//log.Debug("ldapConfs.LdapURL:", ldapConfs.LdapURL)
splitLdapURL := strings.Split(ldapConfs.LdapURL, "://")
protocol, hostport := splitLdapURL[0], splitLdapURL[1]
// Sets a Dial Timeout for LDAP
connectionTimeout := ldapConfs.LdapConnectionTimeout
goldap.DefaultTimeout = time.Duration(connectionTimeout) * time.Second
switch protocol {
case "ldap":
ldap, err = goldap.Dial("tcp", hostport)
case "ldaps":
ldap, err = goldap.DialTLS("tcp", hostport, &tls.Config{InsecureSkipVerify: true})
}
return ldap, err
}
func bindLDAPSearchDN(ldapConfs models.LdapConf, ldap *goldap.Conn) error {
var err error
ldapSearchDn := ldapConfs.LdapSearchDn
ldapSearchPassword := ldapConfs.LdapSearchPassword
err = ldap.Bind(ldapSearchDn, ldapSearchPassword)
if err != nil {
log.Debug("Bind search dn error", err)
return err
}
return nil
}
func searchLDAP(ldapConfs models.LdapConf, ldap *goldap.Conn) (*goldap.SearchResult, error) {
var err error
ldapBaseDn := ldapConfs.LdapBaseDn
ldapScope := ldapConfs.LdapScope
ldapFilter := ldapConfs.LdapFilter
searchRequest := goldap.NewSearchRequest(
ldapBaseDn,
ldapScope,
goldap.NeverDerefAliases,
0, // Unlimited results.
0, // Search Timeout.
false, // Types Only
ldapFilter,
attributes,
nil,
)
result, err := ldap.Search(searchRequest)
if err != nil {
log.Debug("LDAP search error", err)
return nil, err
}
return result, nil
}

View File

@ -0,0 +1,258 @@
/*
Copyright (c) 2016 VMware, Inc. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package ldap
import (
//"fmt"
//"strings"
"os"
"testing"
"github.com/vmware/harbor/src/common/config"
"github.com/vmware/harbor/src/common/dao"
"github.com/vmware/harbor/src/common/models"
"github.com/vmware/harbor/src/common/utils/log"
"github.com/vmware/harbor/src/common/utils/test"
uiConfig "github.com/vmware/harbor/src/ui/config"
)
var adminServerLdapTestConfig = map[string]interface{}{
config.ExtEndpoint: "host01.com",
config.AUTHMode: "ldap_auth",
config.DatabaseType: "mysql",
config.MySQLHost: "127.0.0.1",
config.MySQLPort: 3306,
config.MySQLUsername: "root",
config.MySQLPassword: "root123",
config.MySQLDatabase: "registry",
config.SQLiteFile: "/tmp/registry.db",
//config.SelfRegistration: true,
config.LDAPURL: "ldap://127.0.0.1",
config.LDAPSearchDN: "cn=admin,dc=example,dc=com",
config.LDAPSearchPwd: "admin",
config.LDAPBaseDN: "dc=example,dc=com",
config.LDAPUID: "uid",
config.LDAPFilter: "",
config.LDAPScope: 3,
config.LDAPTimeout: 30,
// config.TokenServiceURL: "",
// config.RegistryURL: "",
// config.EmailHost: "",
// config.EmailPort: 25,
// config.EmailUsername: "",
// config.EmailPassword: "password",
// config.EmailFrom: "from",
// config.EmailSSL: true,
// config.EmailIdentity: "",
// config.ProjectCreationRestriction: config.ProCrtRestrAdmOnly,
// config.VerifyRemoteCert: false,
// config.MaxJobWorkers: 3,
// config.TokenExpiration: 30,
config.CfgExpiration: 5,
// config.JobLogDir: "/var/log/jobs",
// config.UseCompressedJS: true,
config.AdminInitialPassword: "password",
}
func TestMain(t *testing.T) {
server, err := test.NewAdminserver(adminServerLdapTestConfig)
if err != nil {
t.Fatalf("failed to create a mock admin server: %v", err)
}
defer server.Close()
if err := os.Setenv("ADMIN_SERVER_URL", server.URL); err != nil {
t.Fatalf("failed to set env %s: %v", "ADMIN_SERVER_URL", err)
}
secretKeyPath := "/tmp/secretkey"
_, err = test.GenerateKey(secretKeyPath)
if err != nil {
t.Errorf("failed to generate secret key: %v", err)
return
}
defer os.Remove(secretKeyPath)
if err := os.Setenv("KEY_PATH", secretKeyPath); err != nil {
t.Fatalf("failed to set env %s: %v", "KEY_PATH", err)
}
if err := uiConfig.Init(); err != nil {
t.Fatalf("failed to initialize configurations: %v", err)
}
// if err := uiConfig.Load(); err != nil {
// t.Fatalf("failed to load configurations: %v", err)
// }
// mode, err := uiConfig.AuthMode()
// if err != nil {
// t.Fatalf("failed to get auth mode: %v", err)
// }
database, err := uiConfig.Database()
if err != nil {
log.Fatalf("failed to get database configuration: %v", err)
}
if err := dao.InitDatabase(database); err != nil {
log.Fatalf("failed to initialize database: %v", err)
}
}
func TestGetSystemLdapConf(t *testing.T) {
testLdapConfig, err := GetSystemLdapConf()
if err != nil {
t.Fatalf("failed to get system ldap config %v", err)
}
if testLdapConfig.LdapURL != "ldap://127.0.0.1" {
t.Errorf("unexpected LdapURL: %s != %s", testLdapConfig.LdapURL, "ldap://test.ldap.com")
}
}
func TestValidateLdapConf(t *testing.T) {
testLdapConfig, err := GetSystemLdapConf()
if err != nil {
t.Fatalf("failed to get system ldap config %v", err)
}
testLdapConfig, err = ValidateLdapConf(testLdapConfig)
if testLdapConfig.LdapScope != 2 {
t.Errorf("unexpected LdapScope: %d != %d", testLdapConfig.LdapScope, 2)
}
}
func TestMakeFilter(t *testing.T) {
testLdapConfig, err := GetSystemLdapConf()
if err != nil {
t.Fatalf("failed to get system ldap config %v", err)
}
testLdapConfig.LdapFilter = "(ou=people)"
tempUsername := ""
tempFilter := MakeFilter(tempUsername, testLdapConfig.LdapFilter, testLdapConfig.LdapUID)
if tempFilter != "(&(ou=people)(uid=*))" {
t.Errorf("unexpected tempFilter: %s != %s", tempFilter, "(&(ou=people)(uid=*))")
}
tempUsername = "user0001"
tempFilter = MakeFilter(tempUsername, testLdapConfig.LdapFilter, testLdapConfig.LdapUID)
if tempFilter != "(&(ou=people)(uid=user0001))" {
t.Errorf("unexpected tempFilter: %s != %s", tempFilter, "(&(ou=people)(uid=user0001)")
}
}
func TestFormatLdapURL(t *testing.T) {
testLdapConfig, err := GetSystemLdapConf()
if err != nil {
t.Fatalf("failed to get system ldap config %v", err)
}
testLdapConfig.LdapURL = "test.ldap.com"
tempLdapURL, err := formatLdapURL(testLdapConfig.LdapURL)
if err != nil {
t.Errorf("failed to format Ldap URL %v", err)
}
if tempLdapURL != "ldap://test.ldap.com:389" {
t.Errorf("unexpected tempLdapURL: %s != %s", tempLdapURL, "ldap://test.ldap.com:389")
}
testLdapConfig.LdapURL = "ldaps://test.ldap.com"
tempLdapURL, err = formatLdapURL(testLdapConfig.LdapURL)
if err != nil {
t.Errorf("failed to format Ldap URL %v", err)
}
if tempLdapURL != "ldaps://test.ldap.com:636" {
t.Errorf("unexpected tempLdapURL: %s != %s", tempLdapURL, "ldap://test.ldap.com:636")
}
}
func TestImportUser(t *testing.T) {
var u models.LdapUser
var user models.User
u.Username = "ldapUser0001"
u.Realname = "ldapUser"
_, err := ImportUser(u)
if err != nil {
t.Fatalf("failed to add Ldap user: %v", err)
}
user.Username = "ldapUser0001"
user.Email = "ldapUser0001@placeholder.com"
exist, err := dao.UserExists(user, "username")
if !exist {
t.Errorf("failed to add Ldap username: %v", err)
}
exist, err = dao.UserExists(user, "email")
if !exist {
t.Errorf("failed to add Ldap user email: %v", err)
}
_, err = ImportUser(u)
if err.Error() != "duplicate_username" {
t.Fatalf("failed to checking duplicate user: %v", err)
}
}
func TestConnectTest(t *testing.T) {
testLdapConfig, err := GetSystemLdapConf()
if err != nil {
t.Fatalf("failed to get system ldap config %v", err)
}
testLdapConfig.LdapURL = "ldap://localhost:389"
err = ConnectTest(testLdapConfig)
if err != nil {
t.Errorf("unexpected ldap connect fail: %v", err)
}
}
func TestSearchUser(t *testing.T) {
testLdapConfig, err := GetSystemLdapConf()
if err != nil {
t.Fatalf("failed to get system ldap config %v", err)
}
testLdapConfig.LdapURL = "ldap://localhost:389"
testLdapConfig.LdapFilter = MakeFilter("", testLdapConfig.LdapFilter, testLdapConfig.LdapUID)
ldapUsers, err := SearchUser(testLdapConfig)
if err != nil {
t.Errorf("unexpected ldap search fail: %v", err)
}
if ldapUsers[0].Username != "test" {
t.Errorf("unexpected ldap user search result: %s = %s", "ldapUsers[0].Username", ldapUsers[0].Username)
}
}

View File

@ -1,159 +1,236 @@
/*
Copyright (c) 2016 VMware, Inc. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package api
import (
"fmt"
"net/http"
"strconv"
"strings"
"time"
"crypto/tls"
"github.com/vmware/harbor/src/common/api"
"github.com/vmware/harbor/src/common/dao"
"github.com/vmware/harbor/src/common/models"
"github.com/vmware/harbor/src/common/utils/log"
goldap "gopkg.in/ldap.v2"
)
// LdapAPI handles requesst to /api/ldap/ping /api/ldap/search
type LdapAPI struct {
api.BaseAPI
}
var ldapConfs models.LdapConf
// Prepare ...
func (l *LdapAPI) Prepare() {
userID := l.ValidateUser()
isSysAdmin, err := dao.IsAdminRole(userID)
if err != nil {
log.Errorf("error occurred in IsAdminRole: %v", err)
l.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
}
if !isSysAdmin {
l.CustomAbort(http.StatusForbidden, http.StatusText(http.StatusForbidden))
}
}
// Ping ...
func (l *LdapAPI) Ping() {
l.DecodeJSONReqAndValidate(&ldapConfs)
err := validateLdapReq(ldapConfs)
if err != nil {
log.Errorf("Invalid ldap request, error: %v", err)
l.RenderError(http.StatusBadRequest, fmt.Sprintf("invalid ldap request: %v", err))
return
}
err = connectTest(ldapConfs)
if err != nil {
log.Errorf("Ldap connect fail, error: %v", err)
l.RenderError(http.StatusBadRequest, fmt.Sprintf("ldap connect fail: %v", err))
return
}
}
func validateLdapReq(ldapConfs models.LdapConf) error {
ldapURL := ldapConfs.LdapURL
if ldapURL == "" {
return fmt.Errorf("can not get any available LDAP_URL")
}
log.Debug("ldapURL:", ldapURL)
ldapConnectionTimeout := ldapConfs.LdapConnectionTimeout
log.Debug("ldapConnectionTimeout:", ldapConnectionTimeout)
return nil
}
func connectTest(ldapConfs models.LdapConf) error {
var ldap *goldap.Conn
var protocol, hostport string
var host, port string
var err error
ldapURL := ldapConfs.LdapURL
// This routine keeps compability with the old format used on harbor.cfg
if strings.Contains(ldapURL, "://") {
splitLdapURL := strings.Split(ldapURL, "://")
protocol, hostport = splitLdapURL[0], splitLdapURL[1]
if !((protocol == "ldap") || (protocol == "ldaps")) {
return fmt.Errorf("unknown ldap protocl")
}
} else {
hostport = ldapURL
protocol = "ldap"
}
// This tries to detect the used port, if not defined
if strings.Contains(hostport, ":") {
splitHostPort := strings.Split(hostport, ":")
host, port = splitHostPort[0], splitHostPort[1]
_, error := strconv.Atoi(splitHostPort[1])
if error != nil {
return fmt.Errorf("illegal url format")
}
} else {
host = hostport
switch protocol {
case "ldap":
port = "389"
case "ldaps":
port = "636"
}
}
// Sets a Dial Timeout for LDAP
connectionTimeout := ldapConfs.LdapConnectionTimeout
goldap.DefaultTimeout = time.Duration(connectionTimeout) * time.Second
switch protocol {
case "ldap":
ldap, err = goldap.Dial("tcp", fmt.Sprintf("%s:%s", host, port))
case "ldaps":
ldap, err = goldap.DialTLS("tcp", fmt.Sprintf("%s:%s", host, port), &tls.Config{InsecureSkipVerify: true})
}
if err != nil {
return err
}
defer ldap.Close()
ldapSearchDn := ldapConfs.LdapSearchDn
if ldapSearchDn != "" {
log.Debug("Search DN: ", ldapSearchDn)
ldapSearchPassword := ldapConfs.LdapSearchPassword
err = ldap.Bind(ldapSearchDn, ldapSearchPassword)
if err != nil {
log.Debug("Bind search dn error", err)
return err
}
}
return nil
}
/*
Copyright (c) 2016 VMware, Inc. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package api
import (
"fmt"
"net/http"
"strings"
"github.com/vmware/harbor/src/common/api"
"github.com/vmware/harbor/src/common/dao"
"github.com/vmware/harbor/src/common/models"
ldapUtils "github.com/vmware/harbor/src/common/utils/ldap"
"github.com/vmware/harbor/src/common/utils/log"
)
// LdapAPI handles requesst to /api/ldap/ping /api/ldap/user/search /api/ldap/user/import
type LdapAPI struct {
api.BaseAPI
}
const metaChars = "&|!=~*<>()"
// Prepare ...
func (l *LdapAPI) Prepare() {
userID := l.ValidateUser()
isSysAdmin, err := dao.IsAdminRole(userID)
if err != nil {
log.Errorf("error occurred in IsAdminRole: %v", err)
l.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
}
if !isSysAdmin {
l.CustomAbort(http.StatusForbidden, http.StatusText(http.StatusForbidden))
}
}
// Ping ...
func (l *LdapAPI) Ping() {
var err error
var ldapConfs models.LdapConf
l.Ctx.Input.CopyBody(1 << 32)
if string(l.Ctx.Input.RequestBody) == "" {
ldapConfs, err = ldapUtils.GetSystemLdapConf()
if err != nil {
log.Errorf("Can't load system configuration, error: %v", err)
l.RenderError(http.StatusInternalServerError, fmt.Sprintf("can't load system configuration: %v", err))
return
}
} else {
l.DecodeJSONReqAndValidate(&ldapConfs)
}
ldapConfs, err = ldapUtils.ValidateLdapConf(ldapConfs)
if err != nil {
log.Errorf("Invalid ldap request, error: %v", err)
l.RenderError(http.StatusBadRequest, fmt.Sprintf("invalid ldap request: %v", err))
return
}
err = ldapUtils.ConnectTest(ldapConfs)
if err != nil {
log.Errorf("Ldap connect fail, error: %v", err)
l.RenderError(http.StatusBadRequest, fmt.Sprintf("ldap connect fail: %v", err))
return
}
}
// Search ...
func (l *LdapAPI) Search() {
var err error
var ldapUsers []models.LdapUser
var ldapConfs models.LdapConf
l.Ctx.Input.CopyBody(1 << 32)
if string(l.Ctx.Input.RequestBody) == "" {
ldapConfs, err = ldapUtils.GetSystemLdapConf()
if err != nil {
log.Errorf("Can't load system configuration, error: %v", err)
l.RenderError(http.StatusInternalServerError, fmt.Sprintf("can't load system configuration: %v", err))
return
}
} else {
l.DecodeJSONReqAndValidate(&ldapConfs)
}
ldapConfs, err = ldapUtils.ValidateLdapConf(ldapConfs)
if err != nil {
log.Errorf("Invalid ldap request, error: %v", err)
l.RenderError(http.StatusBadRequest, fmt.Sprintf("invalid ldap request: %v", err))
return
}
searchName := l.GetString("username")
if searchName != "" {
for _, c := range metaChars {
if strings.ContainsRune(searchName, c) {
log.Errorf("the search username contains meta char: %q", c)
l.RenderError(http.StatusBadRequest, fmt.Sprintf("the search username contains meta char: %q", c))
return
}
}
}
ldapConfs.LdapFilter = ldapUtils.MakeFilter(searchName, ldapConfs.LdapFilter, ldapConfs.LdapUID)
ldapUsers, err = ldapUtils.SearchUser(ldapConfs)
if err != nil {
log.Errorf("Ldap search fail, error: %v", err)
l.RenderError(http.StatusBadRequest, fmt.Sprintf("ldap search fail: %v", err))
return
}
l.Data["json"] = ldapUsers
l.ServeJSON()
}
// ImportUser ...
func (l *LdapAPI) ImportUser() {
var ldapImportUsers models.LdapImportUser
var ldapFailedImportUsers []models.LdapFailedImportUser
var ldapConfs models.LdapConf
ldapConfs, err := ldapUtils.GetSystemLdapConf()
if err != nil {
log.Errorf("Can't load system configuration, error: %v", err)
l.RenderError(http.StatusInternalServerError, fmt.Sprintf("can't load system configuration: %v", err))
return
}
l.DecodeJSONReqAndValidate(&ldapImportUsers)
ldapConfs, err = ldapUtils.ValidateLdapConf(ldapConfs)
if err != nil {
log.Errorf("Invalid ldap request, error: %v", err)
l.RenderError(http.StatusBadRequest, fmt.Sprintf("invalid ldap request: %v", err))
return
}
ldapFailedImportUsers, err = importUsers(ldapConfs, ldapImportUsers.LdapUIDList)
if err != nil {
log.Errorf("Ldap import user fail, error: %v", err)
l.RenderError(http.StatusBadRequest, fmt.Sprintf("ldap import user fail: %v", err))
return
}
if len(ldapFailedImportUsers) > 0 {
log.Errorf("Import ldap user have internal error")
l.RenderError(http.StatusInternalServerError, fmt.Sprintf("import ldap user have internal error"))
l.Data["json"] = ldapFailedImportUsers
l.ServeJSON()
return
}
}
func importUsers(ldapConfs models.LdapConf, ldapImportUsers []string) ([]models.LdapFailedImportUser, error) {
var failedImportUser []models.LdapFailedImportUser
var u models.LdapFailedImportUser
tempFilter := ldapConfs.LdapFilter
for _, tempUID := range ldapImportUsers {
u.UID = tempUID
u.Error = ""
if u.UID == "" {
u.Error = "empty_uid"
failedImportUser = append(failedImportUser, u)
continue
}
for _, c := range metaChars {
if strings.ContainsRune(u.UID, c) {
u.Error = "invaild_username"
break
}
}
if u.Error != "" {
failedImportUser = append(failedImportUser, u)
continue
}
ldapConfs.LdapFilter = ldapUtils.MakeFilter(u.UID, tempFilter, ldapConfs.LdapUID)
ldapUsers, err := ldapUtils.SearchUser(ldapConfs)
if err != nil {
u.UID = tempUID
u.Error = "failed_search_user"
failedImportUser = append(failedImportUser, u)
log.Errorf("Invalid ldap search request for %s, error: %v", tempUID, err)
continue
}
if ldapUsers == nil {
u.UID = tempUID
u.Error = "unknown_user"
failedImportUser = append(failedImportUser, u)
continue
}
_, err = ldapUtils.ImportUser(ldapUsers[0])
if err != nil {
u.UID = tempUID
u.Error = err.Error()
failedImportUser = append(failedImportUser, u)
log.Errorf("Can't import user %s, error: %s", tempUID, u.Error)
}
}
return failedImportUser, nil
}

View File

@ -1,95 +0,0 @@
package api
import (
"fmt"
"github.com/stretchr/testify/assert"
"github.com/vmware/harbor/tests/apitests/apilib"
"testing"
)
var ldapConf apilib.LdapConf
func TestLdapPost(t *testing.T) {
fmt.Println("Testing ldap post")
assert := assert.New(t)
apiTest := newHarborAPI()
//case 1: ping ldap server without admin role
CommonAddUser()
code, err := apiTest.LdapPost(*testUser, ldapConf)
if err != nil {
t.Error("Error occured while ping ldap server")
t.Log(err)
} else {
assert.Equal(403, code, "Ping ldap server status should be 403")
}
//case 2: ping ldap server with admin role, but empty ldapConf
code, err = apiTest.LdapPost(*admin, ldapConf)
if err != nil {
t.Error("Error occured while ping ldap server")
t.Log(err)
} else {
assert.Equal(400, code, "Ping ldap server status should be 400")
}
//case 3: ping ldap server with admin role, but bad format of ldapConf
ldapConf.LdapURL = "http://127.0.0.1"
code, err = apiTest.LdapPost(*admin, ldapConf)
if err != nil {
t.Error("Error occured while ping ldap server")
t.Log(err)
} else {
assert.Equal(400, code, "Ping ldap server status should be 400")
}
//case 4: ping ldap server with admin role, but bad format of ldapConf
ldapConf.LdapURL = "127.0.0.1:sss"
code, err = apiTest.LdapPost(*admin, ldapConf)
if err != nil {
t.Error("Error occured while ping ldap server")
t.Log(err)
} else {
assert.Equal(400, code, "Ping ldap server status should be 400")
}
//case 5: ping ldap server with admin role, ldap protocol, without port
ldapConf.LdapURL = "127.0.0.1"
code, err = apiTest.LdapPost(*admin, ldapConf)
if err != nil {
t.Error("Error occured while ping ldap server")
t.Log(err)
} else {
assert.Equal(200, code, "Ping ldap server status should be 200")
}
//not success, will try later
/*
//case 6: ping ldap server with admin role, ldaps protocol without port
ldapConf.LdapURL = "ldaps://127.0.0.1"
code, err = apiTest.LdapPost(*admin, ldapConf)
if err != nil {
t.Error("Error occured while ping ldap server")
t.Log(err)
} else {
assert.Equal(200, code, "Ping ldap server status should be 200")
}*/
//case 7: ping ldap server with admin role, ldap protocol, port, ldapSearchDn, but wrong password
ldapConf.LdapURL = "ldap://127.0.0.1:389"
ldapConf.LdapSearchDn = "cn=admin,dc=example,dc=org"
code, err = apiTest.LdapPost(*admin, ldapConf)
if err != nil {
t.Error("Error occured while ping ldap server")
t.Log(err)
} else {
assert.Equal(400, code, "Ping ldap server status should be 400")
}
//case 8: ping ldap server with admin role, ldap protocol, port, ldapSearchDn, right password
ldapConf.LdapURL = "ldap://127.0.0.1:389"
ldapConf.LdapSearchDn = "cn=admin,dc=example,dc=org"
ldapConf.LdapSearchPassword = "admin"
code, err = apiTest.LdapPost(*admin, ldapConf)
if err != nil {
t.Error("Error occured while ping ldap server")
t.Log(err)
} else {
assert.Equal(200, code, "Ping ldap server status should be 200")
}
CommonDelUser()
}

View File

@ -16,19 +16,14 @@
package ldap
import (
"crypto/tls"
"errors"
"fmt"
"strings"
"time"
"github.com/vmware/harbor/src/common/dao"
"github.com/vmware/harbor/src/common/models"
ldapUtils "github.com/vmware/harbor/src/common/utils/ldap"
"github.com/vmware/harbor/src/common/utils/log"
"github.com/vmware/harbor/src/ui/auth"
"github.com/vmware/harbor/src/ui/config"
goldap "gopkg.in/ldap.v2"
)
// Auth implements Authenticator interface to authenticate against LDAP
@ -36,55 +31,6 @@ type Auth struct{}
const metaChars = "&|!=~*<>()"
// Connect checks the LDAP configuration directives, and connects to the LDAP URL
// Returns an LDAP connection
func Connect(settings *models.LDAP) (*goldap.Conn, error) {
ldapURL := settings.URL
if ldapURL == "" {
return nil, errors.New("can not get any available LDAP_URL")
}
log.Debug("ldapURL:", ldapURL)
// This routine keeps compability with the old format used on harbor.cfg
splitLdapURL := strings.Split(ldapURL, "://")
protocol, hostport := splitLdapURL[0], splitLdapURL[1]
var host, port string
// This tries to detect the used port, if not defined
if strings.Contains(hostport, ":") {
splitHostPort := strings.Split(hostport, ":")
host, port = splitHostPort[0], splitHostPort[1]
} else {
host = hostport
switch protocol {
case "ldap":
port = "389"
case "ldaps":
port = "636"
}
}
// Sets a Dial Timeout for LDAP
goldap.DefaultTimeout = time.Duration(settings.Timeout) * time.Second
var ldap *goldap.Conn
var err error
switch protocol {
case "ldap":
ldap, err = goldap.Dial("tcp", fmt.Sprintf("%s:%s", host, port))
case "ldaps":
ldap, err = goldap.DialTLS("tcp", fmt.Sprintf("%s:%s", host, port), &tls.Config{InsecureSkipVerify: true})
}
if err != nil {
return nil, err
}
return ldap, nil
}
// Authenticate checks user's credential against LDAP based on basedn template and LDAP URL,
// if the check is successful a dummy record will be inserted into DB, such that this user can
// be associated to other entities in the system.
@ -97,105 +43,39 @@ func (l *Auth) Authenticate(m models.AuthModel) (*models.User, error) {
}
}
settings, err := config.LDAP()
ldapConfs, err := ldapUtils.GetSystemLdapConf()
if err != nil {
return nil, err
return nil, fmt.Errorf("can't load system configuration: %v", err)
}
ldap, err := Connect(settings)
ldapConfs, err = ldapUtils.ValidateLdapConf(ldapConfs)
if err != nil {
return nil, err
return nil, fmt.Errorf("invalid ldap request: %v", err)
}
ldapBaseDn := settings.BaseDN
if ldapBaseDn == "" {
return nil, errors.New("can not get any available LDAP_BASE_DN")
}
log.Debug("baseDn:", ldapBaseDn)
ldapConfs.LdapFilter = ldapUtils.MakeFilter(p, ldapConfs.LdapFilter, ldapConfs.LdapUID)
ldapSearchDn := settings.SearchDN
if ldapSearchDn != "" {
log.Debug("Search DN: ", ldapSearchDn)
ldapSearchPwd := settings.SearchPassword
err = ldap.Bind(ldapSearchDn, ldapSearchPwd)
if err != nil {
log.Debug("Bind search dn error", err)
return nil, err
}
}
ldapUsers, err := ldapUtils.SearchUser(ldapConfs)
attrName := settings.UID
filter := settings.Filter
if filter != "" {
filter = "(&" + filter + "(" + attrName + "=" + m.Principal + "))"
} else {
filter = "(" + attrName + "=" + m.Principal + ")"
}
log.Debug("one or more filter", filter)
ldapScope := settings.Scope
var scope int
if ldapScope == 1 {
scope = goldap.ScopeBaseObject
} else if ldapScope == 2 {
scope = goldap.ScopeSingleLevel
} else {
scope = goldap.ScopeWholeSubtree
}
attributes := []string{"uid", "cn", "mail", "email"}
searchRequest := goldap.NewSearchRequest(
ldapBaseDn,
scope,
goldap.NeverDerefAliases,
0, // Unlimited results. TODO: Limit this (as we expect only one result)?
0, // Search Timeout. TODO: Limit this (check what is the unit of timeout) and make configurable
false, // Types Only
filter,
attributes,
nil,
)
result, err := ldap.Search(searchRequest)
if err != nil {
return nil, err
return nil, fmt.Errorf("ldap search fail: %v", err)
}
if len(result.Entries) == 0 {
if len(ldapUsers) == 0 {
log.Warningf("Not found an entry.")
return nil, nil
} else if len(result.Entries) != 1 {
} else if len(ldapUsers) != 1 {
log.Warningf("Found more than one entry.")
return nil, nil
}
entry := result.Entries[0]
bindDN := entry.DN
log.Debug("found entry:", bindDN)
err = ldap.Bind(bindDN, m.Password)
if err != nil {
log.Debug("Bind user error", err)
return nil, err
}
defer ldap.Close()
u := models.User{}
u.Username = ldapUsers[0].Username
u.Email = ldapUsers[0].Email
u.Realname = ldapUsers[0].Realname
for _, attr := range entry.Attributes {
val := attr.Values[0]
switch attr.Name {
case "uid":
u.Realname = val
case "cn":
u.Realname = val
case "mail":
u.Email = val
case "email":
u.Email = val
}
}
u.Username = m.Principal
log.Debug("username:", u.Username, ",email:", u.Email)
exist, err := dao.UserExists(u, "username")
if err != nil {
return nil, err
@ -208,19 +88,21 @@ func (l *Auth) Authenticate(m models.AuthModel) (*models.User, error) {
}
u.UserID = currentUser.UserID
} else {
u.Realname = m.Principal
u.Password = "12345678AbC"
u.Comment = "registered from LDAP."
if u.Email == "" {
u.Email = u.Username + "@placeholder.com"
}
userID, err := dao.Register(u)
// u.Password = "12345678AbC"
// u.Comment = "registered from LDAP."
// if u.Email == "" {
// u.Email = u.Username + "@placeholder.com"
// }
userID, err := ldapUtils.ImportUser(ldapUsers[0])
if err != nil {
return nil, err
log.Errorf("Can't import user %s, error: %v", ldapUsers[0].Username, err)
return nil, fmt.Errorf("can't import user %s, error: %v", ldapUsers[0].Username, err)
}
u.UserID = int(userID)
}
return &u, nil
}
func init() {

View File

@ -1,9 +1,113 @@
package ldap
import (
//"fmt"
//"strings"
"os"
"testing"
"github.com/vmware/harbor/src/common/config"
"github.com/vmware/harbor/src/common/dao"
"github.com/vmware/harbor/src/common/models"
"github.com/vmware/harbor/src/common/utils/log"
"github.com/vmware/harbor/src/common/utils/test"
uiConfig "github.com/vmware/harbor/src/ui/config"
)
func TestMain(t *testing.T) {
var adminServerLdapTestConfig = map[string]interface{}{
config.ExtEndpoint: "host01.com",
config.AUTHMode: "ldap_auth",
config.DatabaseType: "mysql",
config.MySQLHost: "127.0.0.1",
config.MySQLPort: 3306,
config.MySQLUsername: "root",
config.MySQLPassword: "root123",
config.MySQLDatabase: "registry",
config.SQLiteFile: "/tmp/registry.db",
//config.SelfRegistration: true,
config.LDAPURL: "ldap://127.0.0.1",
config.LDAPSearchDN: "cn=admin,dc=example,dc=com",
config.LDAPSearchPwd: "admin",
config.LDAPBaseDN: "dc=example,dc=com",
config.LDAPUID: "uid",
config.LDAPFilter: "",
config.LDAPScope: 3,
config.LDAPTimeout: 30,
// config.TokenServiceURL: "",
// config.RegistryURL: "",
// config.EmailHost: "",
// config.EmailPort: 25,
// config.EmailUsername: "",
// config.EmailPassword: "password",
// config.EmailFrom: "from",
// config.EmailSSL: true,
// config.EmailIdentity: "",
// config.ProjectCreationRestriction: config.ProCrtRestrAdmOnly,
// config.VerifyRemoteCert: false,
// config.MaxJobWorkers: 3,
// config.TokenExpiration: 30,
config.CfgExpiration: 5,
// config.JobLogDir: "/var/log/jobs",
// config.UseCompressedJS: true,
config.AdminInitialPassword: "password",
}
func TestMain(t *testing.T) {
server, err := test.NewAdminserver(adminServerLdapTestConfig)
if err != nil {
t.Fatalf("failed to create a mock admin server: %v", err)
}
defer server.Close()
if err := os.Setenv("ADMIN_SERVER_URL", server.URL); err != nil {
t.Fatalf("failed to set env %s: %v", "ADMIN_SERVER_URL", err)
}
secretKeyPath := "/tmp/secretkey"
_, err = test.GenerateKey(secretKeyPath)
if err != nil {
t.Errorf("failed to generate secret key: %v", err)
return
}
defer os.Remove(secretKeyPath)
if err := os.Setenv("KEY_PATH", secretKeyPath); err != nil {
t.Fatalf("failed to set env %s: %v", "KEY_PATH", err)
}
if err := uiConfig.Init(); err != nil {
t.Fatalf("failed to initialize configurations: %v", err)
}
// if err := uiConfig.Load(); err != nil {
// t.Fatalf("failed to load configurations: %v", err)
// }
// mode, err := uiConfig.AuthMode()
// if err != nil {
// t.Fatalf("failed to get auth mode: %v", err)
// }
database, err := uiConfig.Database()
if err != nil {
log.Fatalf("failed to get database configuration: %v", err)
}
if err := dao.InitDatabase(database); err != nil {
log.Fatalf("failed to initialize database: %v", err)
}
}
func TestAuthenticate(t *testing.T) {
var person models.AuthModel
var auth *Auth
person.Principal = "test"
person.Password = "123456"
user, err := auth.Authenticate(person)
if err != nil {
t.Errorf("unexpected ldap authenticate fail: %v", err)
}
if user.Username != "test" {
t.Errorf("unexpected ldap user authenticate fail: %s = %s", "user.Username", user.Username)
}
}

View File

@ -89,6 +89,8 @@ func initRouters() {
beego.Router("/api/systeminfo/volumes", &api.SystemInfoAPI{}, "get:GetVolumeInfo")
beego.Router("/api/systeminfo/getcert", &api.SystemInfoAPI{}, "get:GetCert")
beego.Router("/api/ldap/ping", &api.LdapAPI{}, "post:Ping")
beego.Router("/api/ldap/users/search", &api.LdapAPI{}, "post:Search")
beego.Router("/api/ldap/users/import", &api.LdapAPI{}, "post:ImportUser")
//external service that hosted on harbor process:
beego.Router("/service/notifications", &service.NotificationHandler{})

View File

@ -33,34 +33,3 @@ services:
- /data/secretkey:/etc/adminserver/key
ports:
- 8888:80
ldap:
image: osixia/openldap:1.1.7
restart: always
environment:
LDAP_LOG_LEVEL: "256"
LDAP_ORGANISATION: "Example Inc."
LDAP_DOMAIN: "example.org"
LDAP_BASE_DN: ""
LDAP_ADMIN_PASSWORD: "admin"
LDAP_CONFIG_PASSWORD: "config"
LDAP_READONLY_USER: "false"
LDAP_BACKEND: "hdb"
LDAP_TLS: "true"
LDAP_TLS_CRT_FILENAME: "ldap.crt"
LDAP_TLS_KEY_FILENAME: "ldap.key"
LDAP_TLS_CA_CRT_FILENAME: "ca.crt"
LDAP_TLS_ENFORCE: "false"
LDAP_TLS_CIPHER_SUITE: "SECURE256:-VERS-SSL3.0"
LDAP_TLS_PROTOCOL_MIN: "3.1"
LDAP_TLS_VERIFY_CLIENT: "demand"
LDAP_REPLICATION: "false"
LDAP_REMOVE_CONFIG_AFTER_SETUP: "true"
LDAP_SSL_HELPER_PREFIX: "ldap"
volumes:
- /var/lib/ldap
- /etc/ldap/slapd.d
- /container/service/slapd/assets/certs/
hostname: "example.org"
ports:
- 389:389
- 636:636

14
tests/ldap_test.ldif Normal file
View File

@ -0,0 +1,14 @@
dn: uid=test,dc=example,dc=com
uid: test
cn: test
sn: 3
objectClass: top
objectClass: posixAccount
objectClass: inetOrgPerson
loginShell: /bin/bash
homeDirectory: /home/test
uidNumber: 1001
gidNumber: 1001
userPassword: 123456
mail: test@example.com
gecos: test

15
tests/ldapprepare.sh Executable file
View File

@ -0,0 +1,15 @@
#!/bin/bash
NAME=ldap_server
docker rm -f $NAME 2>/dev/null
docker run --env LDAP_ORGANISATION="Harbor." \
--env LDAP_DOMAIN="example.com" \
--env LDAP_ADMIN_PASSWORD="admin" \
-p 389:389 \
-p 636:636 \
--detach --name $NAME osixia/openldap:1.1.7
sleep 3
docker cp ldap_test.ldif ldap_server:/
docker exec ldap_server ldapadd -x -D "cn=admin,dc=example,dc=com" -w admin -f /ldap_test.ldif -ZZ