mirror of
https://github.com/goharbor/harbor.git
synced 2024-12-22 16:48:30 +01:00
Merge branch 'master' into newer-ui
This commit is contained in:
commit
2ab653c642
5
.github/ISSUE_TEMPLATE
vendored
Normal file
5
.github/ISSUE_TEMPLATE
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
If you are reporting a problem, please make sure the following information are provided:
|
||||
1)Version of docker engine and docker-compose
|
||||
2)Config files of harbor, you can get them by packaging "Deploy/config" directory
|
||||
3)Log files, you can get them by package the /var/log/harbor/
|
||||
|
@ -33,7 +33,10 @@ http {
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
|
||||
# When setting up Harbor behind other proxy, such as an Nginx instance, remove the below line if the proxy already has similar settings.
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
|
||||
proxy_buffering off;
|
||||
proxy_request_buffering off;
|
||||
}
|
||||
@ -47,7 +50,10 @@ http {
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
|
||||
# When setting up Harbor behind other proxy, such as an Nginx instance, remove the below line if the proxy already has similar settings.
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
|
||||
proxy_buffering off;
|
||||
proxy_request_buffering off;
|
||||
|
||||
@ -58,7 +64,10 @@ http {
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
|
||||
# When setting up Harbor behind other proxy, such as an Nginx instance, remove the below line if the proxy already has similar settings.
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
|
||||
proxy_buffering off;
|
||||
proxy_request_buffering off;
|
||||
}
|
||||
|
@ -47,7 +47,10 @@ http {
|
||||
proxy_set_header Host $http_host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
|
||||
# When setting up Harbor behind other proxy, such as an Nginx instance, remove the below line if the proxy already has similar settings.
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
|
||||
proxy_buffering off;
|
||||
proxy_request_buffering off;
|
||||
}
|
||||
@ -61,7 +64,10 @@ http {
|
||||
proxy_set_header Host $http_host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
|
||||
# When setting up Harbor behind other proxy, such as an Nginx instance, remove the below line if the proxy already has similar settings.
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
|
||||
proxy_buffering off;
|
||||
proxy_request_buffering off;
|
||||
|
||||
@ -72,7 +78,10 @@ http {
|
||||
proxy_set_header Host $http_host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
|
||||
# When setting up Harbor behind other proxy, such as an Nginx instance, remove the below line if the proxy already has similar settings.
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
|
||||
proxy_buffering off;
|
||||
proxy_request_buffering off;
|
||||
}
|
||||
|
@ -7,14 +7,14 @@ services:
|
||||
ports:
|
||||
- 1514:514
|
||||
registry:
|
||||
image: library/registry:2.3.0
|
||||
image: library/registry:2.4.0
|
||||
volumes:
|
||||
- /data/registry:/storage
|
||||
- ./config/registry/:/etc/registry/
|
||||
ports:
|
||||
- 5001:5001
|
||||
command:
|
||||
/etc/registry/config.yml
|
||||
["serve", "/etc/registry/config.yml"]
|
||||
depends_on:
|
||||
- log
|
||||
logging:
|
||||
|
@ -9,14 +9,15 @@ hostname = reg.mydomain.com
|
||||
ui_url_protocol = http
|
||||
|
||||
#Email account settings for sending out password resetting emails.
|
||||
email_server = smtp.mydomain.com
|
||||
email_server = smtp.mydomain.com
|
||||
email_server_port = 25
|
||||
email_username = sample_admin@mydomain.com
|
||||
email_password = abc
|
||||
email_from = admin <sample_admin@mydomain.com>
|
||||
email_ssl = false
|
||||
|
||||
##The password of Harbor admin, change this before any production use.
|
||||
harbor_admin_password= Harbor12345
|
||||
harbor_admin_password = Harbor12345
|
||||
|
||||
##By default the auth mode is db_auth, i.e. the credentials are stored in a local database.
|
||||
#Set it to ldap_auth if you want to verify a user's credentials against an LDAP server.
|
||||
@ -33,4 +34,16 @@ db_password = root123
|
||||
|
||||
#Turn on or off the self-registration feature
|
||||
self_registration = on
|
||||
|
||||
#Turn on or off the customize your certicate
|
||||
customize_crt = on
|
||||
|
||||
#fill in your certicate message
|
||||
crt_country = CN
|
||||
crt_state = State
|
||||
crt_location = CN
|
||||
crt_organization = organization
|
||||
crt_organizationalunit = organizational unit
|
||||
crt_commonname = example.com
|
||||
crt_email = example@example.com
|
||||
#####
|
||||
|
@ -29,12 +29,21 @@ email_server_port = rcp.get("configuration", "email_server_port")
|
||||
email_username = rcp.get("configuration", "email_username")
|
||||
email_password = rcp.get("configuration", "email_password")
|
||||
email_from = rcp.get("configuration", "email_from")
|
||||
email_ssl = rcp.get("configuration", "email_ssl")
|
||||
harbor_admin_password = rcp.get("configuration", "harbor_admin_password")
|
||||
auth_mode = rcp.get("configuration", "auth_mode")
|
||||
ldap_url = rcp.get("configuration", "ldap_url")
|
||||
ldap_basedn = rcp.get("configuration", "ldap_basedn")
|
||||
db_password = rcp.get("configuration", "db_password")
|
||||
self_registration = rcp.get("configuration", "self_registration")
|
||||
customize_crt = rcp.get("configuration", "customize_crt")
|
||||
crt_country = rcp.get("configuration", "crt_country")
|
||||
crt_state = rcp.get("configuration", "crt_state")
|
||||
crt_location = rcp.get("configuration", "crt_location")
|
||||
crt_organization = rcp.get("configuration", "crt_organization")
|
||||
crt_organizationalunit = rcp.get("configuration", "crt_organizationalunit")
|
||||
crt_commonname = rcp.get("configuration", "crt_commonname")
|
||||
crt_email = rcp.get("configuration", "crt_email")
|
||||
########
|
||||
|
||||
base_dir = os.path.dirname(__file__)
|
||||
@ -62,10 +71,12 @@ registry_conf = os.path.join(config_dir, "registry", "config.yml")
|
||||
db_conf_env = os.path.join(config_dir, "db", "env")
|
||||
|
||||
conf_files = [ ui_conf, ui_conf_env, registry_conf, db_conf_env ]
|
||||
for f in conf_files:
|
||||
if os.path.exists(f):
|
||||
print("Clearing the configuration file: %s" % f)
|
||||
os.remove(f)
|
||||
def rmdir(cf):
|
||||
for f in cf:
|
||||
if os.path.exists(f):
|
||||
print("Clearing the configuration file: %s" % f)
|
||||
os.remove(f)
|
||||
rmdir(conf_files)
|
||||
|
||||
render(os.path.join(templates_dir, "ui", "env"),
|
||||
ui_conf_env,
|
||||
@ -73,7 +84,7 @@ render(os.path.join(templates_dir, "ui", "env"),
|
||||
db_password=db_password,
|
||||
ui_url=ui_url,
|
||||
auth_mode=auth_mode,
|
||||
admin_pwd=harbor_admin_password,
|
||||
harbor_admin_password=harbor_admin_password,
|
||||
ldap_url=ldap_url,
|
||||
ldap_basedn=ldap_basedn,
|
||||
self_registration=self_registration)
|
||||
@ -82,9 +93,10 @@ render(os.path.join(templates_dir, "ui", "app.conf"),
|
||||
ui_conf,
|
||||
email_server=email_server,
|
||||
email_server_port=email_server_port,
|
||||
email_user_name=email_username,
|
||||
email_user_password=email_password,
|
||||
email_username=email_username,
|
||||
email_password=email_password,
|
||||
email_from=email_from,
|
||||
email_ssl=email_ssl,
|
||||
ui_url=ui_url)
|
||||
|
||||
render(os.path.join(templates_dir, "registry", "config.yml"),
|
||||
@ -95,4 +107,58 @@ render(os.path.join(templates_dir, "db", "env"),
|
||||
db_conf_env,
|
||||
db_password=db_password)
|
||||
|
||||
def validate_crt_subj(dirty_subj):
|
||||
subj_list = [item for item in dirty_subj.strip().split("/") \
|
||||
if len(item.split("=")) == 2 and len(item.split("=")[1]) > 0]
|
||||
return "/" + "/".join(subj_list)
|
||||
|
||||
FNULL = open(os.devnull, 'w')
|
||||
|
||||
from functools import wraps
|
||||
def stat_decorator(func):
|
||||
#@wraps(func)
|
||||
def check_wrapper(*args, **kwargs):
|
||||
stat = func(*args, **kwargs)
|
||||
message = "Generated configuration file: %s" % kwargs['path'] \
|
||||
if stat == 0 else "Fail to generate %s" % kwargs['path']
|
||||
print(message)
|
||||
if stat != 0:
|
||||
sys.exit(1)
|
||||
return check_wrapper
|
||||
|
||||
@stat_decorator
|
||||
def check_private_key_stat(*args, **kwargs):
|
||||
return subprocess.call(["openssl", "genrsa", "-out", kwargs['path'], "4096"],\
|
||||
stdout=FNULL, stderr=subprocess.STDOUT)
|
||||
|
||||
@stat_decorator
|
||||
def check_certificate_stat(*args, **kwargs):
|
||||
dirty_subj = "/C={0}/ST={1}/L={2}/O={3}/OU={4}/CN={5}/emailAddress={6}"\
|
||||
.format(crt_country, crt_state, crt_location, crt_organization,\
|
||||
crt_organizationalunit, crt_commonname, crt_email)
|
||||
subj = validate_crt_subj(dirty_subj)
|
||||
return subprocess.call(["openssl", "req", "-new", "-x509", "-key",\
|
||||
private_key_pem, "-out", root_crt, "-days", "3650", "-subj", subj], \
|
||||
stdout=FNULL, stderr=subprocess.STDOUT)
|
||||
|
||||
def openssl_is_installed(stat):
|
||||
if stat == 0:
|
||||
return True
|
||||
else:
|
||||
print("Cannot find openssl installed in this computer\nUse default SSL certificate file")
|
||||
return False
|
||||
|
||||
if customize_crt == 'on':
|
||||
import subprocess
|
||||
shell_stat = subprocess.check_call(["which", "openssl"], stdout=FNULL, stderr=subprocess.STDOUT)
|
||||
if openssl_is_installed(shell_stat):
|
||||
private_key_pem = os.path.join(config_dir, "ui", "private_key.pem")
|
||||
root_crt = os.path.join(config_dir, "registry", "root.crt")
|
||||
crt_conf_files = [ private_key_pem, root_crt ]
|
||||
rmdir(crt_conf_files)
|
||||
|
||||
check_private_key_stat(path=private_key_pem)
|
||||
check_certificate_stat(path=root_crt)
|
||||
|
||||
FNULL.close()
|
||||
print("The configuration files are ready, please use docker-compose to start the service.")
|
||||
|
@ -11,6 +11,7 @@ httpport = 80
|
||||
[mail]
|
||||
host = $email_server
|
||||
port = $email_server_port
|
||||
username = $email_user_name
|
||||
password = $email_user_password
|
||||
username = $email_username
|
||||
password = $email_password
|
||||
from = $email_from
|
||||
ssl = $email_ssl
|
||||
|
@ -5,8 +5,8 @@ MYSQL_PWD=$db_password
|
||||
REGISTRY_URL=http://registry:5000
|
||||
CONFIG_PATH=/etc/ui/app.conf
|
||||
HARBOR_REG_URL=$hostname
|
||||
HARBOR_ADMIN_PASSWORD=$admin_pwd
|
||||
HARBOR_URL=$ui_url
|
||||
HARBOR_ADMIN_PASSWORD=$harbor_admin_password
|
||||
HARBOR_URL=$hostname
|
||||
AUTH_MODE=$auth_mode
|
||||
LDAP_URL=$ldap_url
|
||||
LDAP_BASE_DN=$ldap_basedn
|
||||
|
@ -57,8 +57,8 @@ To simplify the installation process, a pre-built installation package of Harbor
|
||||
|
||||
For information on how to use Harbor, please see [User Guide](docs/user_guide.md) .
|
||||
|
||||
### Deploy harbor on Kubernetes
|
||||
Detailed instruction about deploying harbor on Kubernetes is described [here](https://github.com/vmware/harbor/blob/master/kubernetes_deployment.md).
|
||||
### Deploy Harbor on Kubernetes
|
||||
Detailed instruction about deploying Harbor on Kubernetes is described [here](docs/kubernetes_deployment.md).
|
||||
|
||||
### Contribution
|
||||
We welcome contributions from the community. If you wish to contribute code and you have not signed our contributor license agreement (CLA), our bot will update the issue when you open a pull request. For any questions about the CLA process, please refer to our [FAQ](https://cla.vmware.com/faq).
|
||||
|
48
api/user.go
48
api/user.go
@ -36,6 +36,11 @@ type UserAPI struct {
|
||||
AuthMode string
|
||||
}
|
||||
|
||||
type passwordReq struct {
|
||||
OldPassword string `json:"old_password"`
|
||||
NewPassword string `json:"new_password"`
|
||||
}
|
||||
|
||||
// Prepare validates the URL and parms
|
||||
func (ua *UserAPI) Prepare() {
|
||||
|
||||
@ -177,3 +182,46 @@ func (ua *UserAPI) Delete() {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// ChangePassword handles PUT to /api/users/{}/password
|
||||
func (ua *UserAPI) ChangePassword() {
|
||||
|
||||
if !(ua.AuthMode == "db_auth") {
|
||||
ua.CustomAbort(http.StatusForbidden, "")
|
||||
}
|
||||
|
||||
if !ua.IsAdmin {
|
||||
if ua.userID != ua.currentUserID {
|
||||
log.Error("Guests can only change their own account.")
|
||||
ua.CustomAbort(http.StatusForbidden, "Guests can only change their own account.")
|
||||
}
|
||||
}
|
||||
|
||||
var req passwordReq
|
||||
ua.DecodeJSONReq(&req)
|
||||
if req.OldPassword == "" {
|
||||
log.Error("Old password is blank")
|
||||
ua.CustomAbort(http.StatusBadRequest, "Old password is blank")
|
||||
}
|
||||
|
||||
queryUser := models.User{UserID: ua.userID, Password: req.OldPassword}
|
||||
user, err := dao.CheckUserPassword(queryUser)
|
||||
if err != nil {
|
||||
log.Errorf("Error occurred in CheckUserPassword: %v", err)
|
||||
ua.CustomAbort(http.StatusInternalServerError, "Internal error.")
|
||||
}
|
||||
if user == nil {
|
||||
log.Warning("Password input is not correct")
|
||||
ua.CustomAbort(http.StatusForbidden, "old_password_is_not_correct")
|
||||
}
|
||||
|
||||
if req.NewPassword == "" {
|
||||
ua.CustomAbort(http.StatusBadRequest, "please_input_new_password")
|
||||
}
|
||||
updateUser := models.User{UserID: ua.userID, Password: req.NewPassword, Salt: user.Salt}
|
||||
err = dao.ChangeUserPassword(updateUser, req.OldPassword)
|
||||
if err != nil {
|
||||
log.Errorf("Error occurred in ChangeUserPassword: %v", err)
|
||||
ua.CustomAbort(http.StatusInternalServerError, "Internal error.")
|
||||
}
|
||||
}
|
||||
|
@ -76,31 +76,25 @@ func (l *Auth) Authenticate(m models.AuthModel) (*models.User, error) {
|
||||
|
||||
scope := openldap.LDAP_SCOPE_SUBTREE // LDAP_SCOPE_BASE, LDAP_SCOPE_ONELEVEL, LDAP_SCOPE_SUBTREE
|
||||
filter := "objectClass=*"
|
||||
attributes := []string{"cn", "mail", "uid"}
|
||||
attributes := []string{"mail"}
|
||||
|
||||
result, err := ldap.SearchAll(baseDn, scope, filter, attributes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(result.Entries()) != 1 {
|
||||
log.Warningf("Found more than one entry.")
|
||||
return nil, nil
|
||||
}
|
||||
en := result.Entries()[0]
|
||||
u := models.User{}
|
||||
for _, attr := range en.Attributes() {
|
||||
val := attr.Values()[0]
|
||||
switch attr.Name() {
|
||||
case "uid":
|
||||
u.Username = val
|
||||
case "mail":
|
||||
u.Email = val
|
||||
case "cn":
|
||||
u.Realname = val
|
||||
if len(result.Entries()) == 1 {
|
||||
en := result.Entries()[0]
|
||||
for _, attr := range en.Attributes() {
|
||||
val := attr.Values()[0]
|
||||
if attr.Name() == "mail" {
|
||||
u.Email = val
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log.Debug("username:", u.Username, ",email:", u.Email, ",realname:", u.Realname)
|
||||
u.Username = m.Principal
|
||||
log.Debug("username:", u.Username, ",email:", u.Email)
|
||||
|
||||
exist, err := dao.UserExists(u, "username")
|
||||
if err != nil {
|
||||
@ -114,6 +108,7 @@ 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."
|
||||
userID, err := dao.Register(u)
|
||||
|
68
contrib/docker-compose.yml.daocloud
Normal file
68
contrib/docker-compose.yml.daocloud
Normal file
@ -0,0 +1,68 @@
|
||||
version: '2'
|
||||
services:
|
||||
log:
|
||||
image: daocloud.io/harbor/deploy_log:latest
|
||||
volumes:
|
||||
- /var/log/harbor/:/var/log/docker/
|
||||
ports:
|
||||
- 1514:514
|
||||
registry:
|
||||
image: daocloud.io/library/registry:2.3.0
|
||||
volumes:
|
||||
- /data/registry:/storage
|
||||
- ./config/registry/:/etc/registry/
|
||||
ports:
|
||||
- 5001:5001
|
||||
command:
|
||||
/etc/registry/config.yml
|
||||
depends_on:
|
||||
- log
|
||||
logging:
|
||||
driver: "syslog"
|
||||
options:
|
||||
syslog-address: "tcp://127.0.0.1:1514"
|
||||
syslog-tag: "registry"
|
||||
mysql:
|
||||
image: daocloud.io/harbor/deploy_mysql:latest
|
||||
volumes:
|
||||
- /data/database:/var/lib/mysql
|
||||
env_file:
|
||||
- ./config/db/env
|
||||
depends_on:
|
||||
- log
|
||||
logging:
|
||||
driver: "syslog"
|
||||
options:
|
||||
syslog-address: "tcp://127.0.0.1:1514"
|
||||
syslog-tag: "mysql"
|
||||
ui:
|
||||
image: daocloud.io/harbor/deploy_ui:latest
|
||||
env_file:
|
||||
- ./config/ui/env
|
||||
volumes:
|
||||
- ./config/ui/app.conf:/etc/ui/app.conf
|
||||
- ./config/ui/private_key.pem:/etc/ui/private_key.pem
|
||||
depends_on:
|
||||
- log
|
||||
logging:
|
||||
driver: "syslog"
|
||||
options:
|
||||
syslog-address: "tcp://127.0.0.1:1514"
|
||||
syslog-tag: "ui"
|
||||
proxy:
|
||||
image: daocloud.io/library/nginx:1.9
|
||||
volumes:
|
||||
- ./config/nginx:/etc/nginx
|
||||
ports:
|
||||
- 80:80
|
||||
- 443:443
|
||||
depends_on:
|
||||
- mysql
|
||||
- registry
|
||||
- ui
|
||||
- log
|
||||
logging:
|
||||
driver: "syslog"
|
||||
options:
|
||||
syslog-address: "tcp://127.0.0.1:1514"
|
||||
syslog-tag: "proxy"
|
@ -46,47 +46,6 @@ func (cpc *ChangePasswordController) Get() {
|
||||
cpc.ForwardTo("page_title_change_password", "change-password")
|
||||
}
|
||||
|
||||
// UpdatePassword handles UI request to update user's password, it only works when the auth mode is db_auth.
|
||||
func (cc *CommonController) UpdatePassword() {
|
||||
|
||||
sessionUserID := cc.GetSession("userId")
|
||||
|
||||
if sessionUserID == nil {
|
||||
log.Warning("User does not login.")
|
||||
cc.CustomAbort(http.StatusUnauthorized, "please_login_first")
|
||||
}
|
||||
|
||||
oldPassword := cc.GetString("old_password")
|
||||
if oldPassword == "" {
|
||||
log.Error("Old password is blank")
|
||||
cc.CustomAbort(http.StatusBadRequest, "Old password is blank")
|
||||
}
|
||||
|
||||
queryUser := models.User{UserID: sessionUserID.(int), Password: oldPassword}
|
||||
user, err := dao.CheckUserPassword(queryUser)
|
||||
if err != nil {
|
||||
log.Errorf("Error occurred in CheckUserPassword: %v", err)
|
||||
cc.CustomAbort(http.StatusInternalServerError, "Internal error.")
|
||||
}
|
||||
|
||||
if user == nil {
|
||||
log.Warning("Password input is not correct")
|
||||
cc.CustomAbort(http.StatusForbidden, "old_password_is_not_correct")
|
||||
}
|
||||
|
||||
password := cc.GetString("password")
|
||||
if password != "" {
|
||||
updateUser := models.User{UserID: sessionUserID.(int), Password: password, Salt: user.Salt}
|
||||
err = dao.ChangeUserPassword(updateUser, oldPassword)
|
||||
if err != nil {
|
||||
log.Errorf("Error occurred in ChangeUserPassword: %v", err)
|
||||
cc.CustomAbort(http.StatusInternalServerError, "Internal error.")
|
||||
}
|
||||
} else {
|
||||
cc.CustomAbort(http.StatusBadRequest, "please_input_new_password")
|
||||
}
|
||||
}
|
||||
|
||||
// ForgotPasswordController handles request to /forgotPassword
|
||||
type ForgotPasswordController struct {
|
||||
BaseController
|
||||
|
@ -77,7 +77,7 @@ func InitDB() {
|
||||
var err error
|
||||
var c net.Conn
|
||||
for {
|
||||
c, err = net.Dial("tcp", addr+":"+port)
|
||||
c, err = net.DialTimeout("tcp", addr+":"+port, 20*time.Second)
|
||||
if err == nil {
|
||||
c.Close()
|
||||
ch <- 1
|
||||
|
@ -74,16 +74,13 @@ func validate(user models.User) error {
|
||||
return errors.New("Username already exists.")
|
||||
}
|
||||
|
||||
if m, _ := regexp.MatchString(`^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$`, user.Email); !m {
|
||||
return errors.New("Email with illegal format.")
|
||||
}
|
||||
|
||||
if isIllegalLength(user.Email, 0, -1) {
|
||||
return errors.New("Email cannot empty.")
|
||||
}
|
||||
|
||||
if exist, _ := UserExists(models.User{Email: user.Email}, "email"); exist {
|
||||
return errors.New("Email already exists.")
|
||||
if len(user.Email) > 0 {
|
||||
if m, _ := regexp.MatchString(`^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$`, user.Email); !m {
|
||||
return errors.New("Email with illegal format.")
|
||||
}
|
||||
if exist, _ := UserExists(models.User{Email: user.Email}, "email"); exist {
|
||||
return errors.New("Email already exists.")
|
||||
}
|
||||
}
|
||||
|
||||
if isIllegalLength(user.Realname, 0, 20) {
|
||||
|
@ -124,3 +124,4 @@ After setting up HTTPS for Harbor, you can verify it by the follow steps:
|
||||
cp yourdomain.com.crt /etc/pki/ca-trust/source/anchors/reg.yourdomain.com.crt
|
||||
update-ca-trust
|
||||
```
|
||||
|
@ -22,6 +22,10 @@ From time to time, you may need to mannually test Harbor REST API. You can deplo
|
||||
```sh
|
||||
vi prepare-swagger.sh
|
||||
```
|
||||
* Change the SCHEME to the protocol scheme of your Harbor server.
|
||||
```sh
|
||||
SCHEME=<HARBOR_SERVER_SCHEME>
|
||||
```
|
||||
* Change the SERVER_IP to the IP address of your Harbor server.
|
||||
```sh
|
||||
SERVER_ID=<HARBOR_SERVER_DOMAIN>
|
||||
|
60
docs/customize_token_service.md
Normal file
60
docs/customize_token_service.md
Normal file
@ -0,0 +1,60 @@
|
||||
#Customize Harbor token service with your key and certificate
|
||||
|
||||
Harbor requires Docker client to access the Harbor registry with a token. The procedure to generate a token is like [Docker Registry v2 authentication](https://github.com/docker/distribution/blob/master/docs/spec/auth/token.md). Firstly, you should make a request to the token service for a token. The token is signed by the private key. After that, you make a new request with the token to the Harbor registry, Harbor registry will verify the token with the public key in the rootcert bundle. Then Harbor registry will authorize the Docker client to push/pull images.
|
||||
|
||||
By default, Harbor uses default private key and certificate in authentication. Also, you can customize your configuration with your own key and certificate with the following steps:
|
||||
|
||||
1.If you already have a certificate, go to step 3.
|
||||
|
||||
2.If not, you can generate a root certificate using openSSL with following commands:
|
||||
|
||||
**1)Generate a private key:**
|
||||
|
||||
```sh
|
||||
$ openssl genrsa -out private_key.pem 4096
|
||||
```
|
||||
|
||||
**2)Generate a certificate:**
|
||||
```sh
|
||||
$ openssl req -new -x509 -key private_key.pem -out root.crt -days 3650
|
||||
```
|
||||
You are about to be asked to enter information that will be incorporated into your certificate request.
|
||||
What you are about to enter is what is called a Distinguished Name or a DN.
|
||||
There are quite a few fields but you can leave some blank
|
||||
For some fields there will be a default value,
|
||||
If you enter '.', the field will be left blank. Following are what you're asked to enter.
|
||||
|
||||
Country Name (2 letter code) [AU]:
|
||||
|
||||
State or Province Name (full name) [Some-State]:
|
||||
|
||||
Locality Name (eg, city) []:
|
||||
|
||||
Organization Name (eg, company) [Internet Widgits Pty Ltd]:
|
||||
|
||||
Organizational Unit Name (eg, section) []:
|
||||
|
||||
Common Name (eg, server FQDN or YOUR name) []:
|
||||
|
||||
Email Address []:
|
||||
|
||||
After you execute these two commands, you will see private_key.pem and root.crt in the **current directory**, just type "ls", you'll see them.
|
||||
|
||||
3.Refer to [Installation Guide](https://github.com/vmware/harbor/blob/master/docs/installation_guide.md) to install Harbor, After you execute ./prepare, Harbor generates several config files. We need to replace the original private key and certificate with your own key and certificate.
|
||||
|
||||
4.Replace the default key and certificate. Assume that you key and certificate are in the directory /root/cert, following are what you should do:
|
||||
|
||||
```
|
||||
$ cd config/ui
|
||||
$ cp /root/cert/private_key.pem private_key.pem
|
||||
$ cp /root/cert/root.crt ../registry/root.crt
|
||||
```
|
||||
|
||||
5.After these, go back to the Deploy directory, you can start Harbor using following command:
|
||||
```
|
||||
$ docker-compose up -d
|
||||
```
|
||||
|
||||
6.Then you can push/pull images to see if your own certificate works. Please refer [User Guide](https://github.com/vmware/harbor/blob/master/docs/user_guide.md) for more info.
|
||||
|
||||
|
11
docs/image_pulling_chinese_user.md
Normal file
11
docs/image_pulling_chinese_user.md
Normal file
@ -0,0 +1,11 @@
|
||||
### A faster way to pull images for Chinese Harbor users
|
||||
By default, Harbor not only build images according to Dockerfile but also pull images from Docker Hub. For the reason we all know, it is difficult for Chinese Harbor users to pull images from the Docker Hub. We put images on daocloud.io platform, we'll put images on other platforms later. If you have difficulty to pull images from Docker Hub, or you think it wastes too much time to build images. We recommend you to use the following way to accelerate the pulling procedure(make sure you're in the harbor diectory):
|
||||
```
|
||||
$ cd contrib
|
||||
$ cp docker-compose.yml.daocloud ../Deploy
|
||||
$ cd ../Deploy
|
||||
$ mv docker-compose.yml docker-compose.yml.bak
|
||||
$ mv docker-compose.yml.daocloud docker-compose.yml
|
||||
$ docker-compose up -d
|
||||
```
|
||||
Then you'll see docker pulling imges faster than before.
|
@ -29,12 +29,13 @@ At minimum, you need to change the **hostname** attribute in **harbor.cfg**. The
|
||||
|
||||
**hostname**: The hostname for a user to access the user interface and the registry service. It should be the IP address or the fully qualified domain name (FQDN) of your target machine, for example 192.168.1.10 or reg.yourdomain.com . Do NOT use localhost or 127.0.0.1 for the hostname because the registry service needs to be accessed by external clients.
|
||||
**ui_url_protocol**: The protocol for accessing the user interface and the token/notification service, by default it is http. To set up the https protocol, refer to [Configuring Harbor with HTTPS Access](configure_https.md).
|
||||
**Email settings**: the following 5 attributes are used to send an email to reset a user's password, they are not mandatory unless the password reset function is needed in Harbor.
|
||||
**Email settings**: the following 6 attributes are used to send an email to reset a user's password, they are not mandatory unless the password reset function is needed in Harbor. By default SSL connection is not enabled, if your smtp server(such as exmail.qq.com) requires SSL connection and doesn't support STARTTLS, then you should enable it by set **email_ssl = true**.
|
||||
* email_server = smtp.mydomain.com
|
||||
* email_server_port = 25
|
||||
* email_username = sample_admin@mydomain.com
|
||||
* email_password = abc
|
||||
* email_from = admin <sample_admin@mydomain.com>
|
||||
* email_ssl = false
|
||||
|
||||
**harbor_admin_password**: The password for the administrator of Harbor, by default the password is Harbor12345, the user name is admin.
|
||||
**auth_mode**: The authentication mode of Harbor. By default it is *db_auth*, i.e. the credentials are stored in a database. Please set it to *ldap_auth* if you want to verify user's credentials against an LDAP server.
|
||||
@ -203,4 +204,10 @@ $ rm -r /data/registry
|
||||
[Docker Compose command-line reference](https://docs.docker.com/compose/reference/) describes the usage information for the docker-compose subcommands.
|
||||
|
||||
### Persistent data and log files
|
||||
By default, the data of database and image files in the registry are persisted in the directory **/data/** of the target machine. When Harbor's containers are removed and recreated, the data remain unchanged. Harbor leverages rsyslog to collect the logs of each container, by default the log files are stored in the directory **/var/log/harbor/** on Harbor's host.
|
||||
By default, the data of database and image files in the registry are persisted in the directory **/data/** of the target machine. When Harbor's containers are removed and recreated, the data remain unchanged. Harbor leverages rsyslog to collect the logs of each container, by default the log files are stored in the directory **/var/log/harbor/** on Harbor's host.
|
||||
|
||||
##Troubleshooting
|
||||
1.When setting up Harbor behind another nginx proxy or elastic load balancing, remove the below line if the proxy already has similar settings. Be sure to remove the line under these 3 sections: "location /", "location /v2/" and "location /service/".
|
||||
```
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
```
|
||||
|
@ -1,5 +1,6 @@
|
||||
#!/bin/bash
|
||||
SERVER_IP=10.117.170.65
|
||||
SCHEME=http
|
||||
SERVER_IP=reg.mydomain.com
|
||||
set -e
|
||||
echo "Doing some clean up..."
|
||||
rm -f *.tar.gz
|
||||
@ -8,9 +9,10 @@ wget https://github.com/swagger-api/swagger-ui/archive/v2.1.4.tar.gz -O swagger.
|
||||
echo "Untarring Swagger UI package to the static file path..."
|
||||
tar -C ../static/vendors -zxf swagger.tar.gz swagger-ui-2.1.4/dist
|
||||
echo "Executing some processes..."
|
||||
sed -i 's/http:\/\/petstore\.swagger\.io\/v2\/swagger\.json/http:\/\/'$SERVER_IP'\/static\/resources\/yaml\/swagger\.yaml/g' \
|
||||
sed -i 's/http:\/\/petstore\.swagger\.io\/v2\/swagger\.json/'$SCHEME':\/\/'$SERVER_IP'\/static\/resources\/yaml\/swagger\.yaml/g' \
|
||||
../static/vendors/swagger-ui-2.1.4/dist/index.html
|
||||
mkdir -p ../static/resources/yaml
|
||||
cp swagger.yaml ../static/resources/yaml
|
||||
sed -i 's/host: localhost/host: '$SERVER_IP'/g' ../static/resources/yaml/swagger.yaml
|
||||
sed -i 's/ \- http$/ \- '$SCHEME'/g' ../static/resources/yaml/swagger.yaml
|
||||
echo "Finish preparation for the Swagger UI."
|
||||
|
@ -56,16 +56,18 @@ jQuery(function(){
|
||||
validateOptions.Validate(function(){
|
||||
var oldPassword = $("#OldPassword").val();
|
||||
var password = $("#Password").val();
|
||||
$.ajax({
|
||||
"url": "/updatePassword",
|
||||
"type": "post",
|
||||
"data": {"old_password": oldPassword, "password" : password},
|
||||
"beforeSend": function(e){
|
||||
new AjaxUtil({
|
||||
url: "/api/users/current/password",
|
||||
type: "put",
|
||||
data: {"old_password": oldPassword, "new_password" : password},
|
||||
beforeSend: function(e){
|
||||
unbindEnterKey();
|
||||
$("h1").append(spinner.el);
|
||||
$("#btnSubmit").prop("disabled", true);
|
||||
},
|
||||
"success": function(data, status, xhr){
|
||||
complete: function(xhr, status){
|
||||
spinner.stop();
|
||||
$("#btnSubmit").prop("disabled", false);
|
||||
if(xhr && xhr.status == 200){
|
||||
$("#dlgModal")
|
||||
.dialogModal({
|
||||
@ -77,22 +79,20 @@ jQuery(function(){
|
||||
});
|
||||
}
|
||||
},
|
||||
"error": function(jqXhr, status, error){
|
||||
$("#dlgModal")
|
||||
.dialogModal({
|
||||
"title": i18n.getMessage("title_change_password"),
|
||||
"content": i18n.getMessage(jqXhr.responseText),
|
||||
"callback": function(){
|
||||
bindEnterKey();
|
||||
return;
|
||||
}
|
||||
});
|
||||
},
|
||||
"complete": function(){
|
||||
spinner.stop();
|
||||
$("#btnSubmit").prop("disabled", false);
|
||||
error: function(jqXhr, status, error){
|
||||
if(jqXhr && jqXhr.responseText.length){
|
||||
$("#dlgModal")
|
||||
.dialogModal({
|
||||
"title": i18n.getMessage("title_change_password"),
|
||||
"content": i18n.getMessage(jqXhr.responseText),
|
||||
"callback": function(){
|
||||
bindEnterKey();
|
||||
return;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}).exec();
|
||||
});
|
||||
});
|
||||
});
|
@ -36,7 +36,6 @@ func initRouters() {
|
||||
beego.Router("/userExists", &controllers.CommonController{}, "post:UserExists")
|
||||
beego.Router("/reset", &controllers.CommonController{}, "post:ResetPassword")
|
||||
beego.Router("/sendEmail", &controllers.CommonController{}, "get:SendEmail")
|
||||
beego.Router("/updatePassword", &controllers.CommonController{}, "post:UpdatePassword")
|
||||
|
||||
beego.Router("/", &controllers.IndexController{})
|
||||
beego.Router("/signIn", &controllers.SignInController{})
|
||||
@ -58,6 +57,7 @@ func initRouters() {
|
||||
beego.Router("/api/projects/:id/logs/filter", &api.ProjectAPI{}, "post:FilterAccessLog")
|
||||
beego.Router("/api/users", &api.UserAPI{})
|
||||
beego.Router("/api/users/?:id", &api.UserAPI{})
|
||||
beego.Router("/api/users/:id/password", &api.UserAPI{}, "put:ChangePassword")
|
||||
beego.Router("/api/repositories", &api.RepositoryAPI{})
|
||||
beego.Router("/api/repositories/tags", &api.RepositoryAPI{}, "get:GetTags")
|
||||
beego.Router("/api/repositories/manifests", &api.RepositoryAPI{}, "get:GetManifests")
|
||||
|
@ -17,6 +17,8 @@ package utils
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/tls"
|
||||
"strings"
|
||||
|
||||
"net/smtp"
|
||||
"text/template"
|
||||
@ -39,6 +41,7 @@ type MailConfig struct {
|
||||
Port string
|
||||
Username string
|
||||
Password string
|
||||
TLS bool
|
||||
}
|
||||
|
||||
var mc MailConfig
|
||||
@ -58,10 +61,66 @@ func (m Mail) SendMail() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return smtp.
|
||||
SendMail(mc.Host+":"+mc.Port,
|
||||
smtp.PlainAuth(mc.Identity, mc.Username, mc.Password, mc.Host),
|
||||
m.From, m.To, mailContent.Bytes())
|
||||
content := mailContent.Bytes()
|
||||
|
||||
auth := smtp.PlainAuth(mc.Identity, mc.Username, mc.Password, mc.Host)
|
||||
if mc.TLS {
|
||||
err = sendMailWithTLS(m, auth, content)
|
||||
} else {
|
||||
err = sendMail(m, auth, content)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func sendMail(m Mail, auth smtp.Auth, content []byte) error {
|
||||
return smtp.SendMail(mc.Host+":"+mc.Port, auth, m.From, m.To, content)
|
||||
}
|
||||
|
||||
func sendMailWithTLS(m Mail, auth smtp.Auth, content []byte) error {
|
||||
conn, err := tls.Dial("tcp", mc.Host+":"+mc.Port, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
client, err := smtp.NewClient(conn, mc.Host)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
if ok, _ := client.Extension("AUTH"); ok {
|
||||
if err = client.Auth(auth); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err = client.Mail(m.From); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, to := range m.To {
|
||||
if err = client.Rcpt(to); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
w, err := client.Data()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = w.Write(content)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = w.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return client.Quit()
|
||||
}
|
||||
|
||||
func loadConfig() {
|
||||
@ -69,11 +128,17 @@ func loadConfig() {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
var useTLS = false
|
||||
if config["ssl"] != "" && strings.ToLower(config["ssl"]) == "true" {
|
||||
useTLS = true
|
||||
}
|
||||
mc = MailConfig{
|
||||
Identity: "Mail Config",
|
||||
Host: config["host"],
|
||||
Port: config["port"],
|
||||
Username: config["username"],
|
||||
Password: config["password"],
|
||||
TLS: useTLS,
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user