mirror of
https://github.com/goharbor/harbor.git
synced 2025-02-23 07:11:36 +01:00
Use AES to encrypt password for target
This commit is contained in:
parent
fa18ab3a75
commit
318d10186e
@ -28,6 +28,8 @@ env:
|
|||||||
HARBOR_ADMIN: admin
|
HARBOR_ADMIN: admin
|
||||||
HARBOR_ADMIN_PASSWD: Harbor12345
|
HARBOR_ADMIN_PASSWD: Harbor12345
|
||||||
UI_SECRET: tempString
|
UI_SECRET: tempString
|
||||||
|
MAX_JOB_WORKERS: 3
|
||||||
|
SECRET_KEY: 1234567890123456
|
||||||
|
|
||||||
before_install:
|
before_install:
|
||||||
- sudo ./tests/hostcfg.sh
|
- sudo ./tests/hostcfg.sh
|
||||||
|
@ -122,7 +122,7 @@ create table replication_target (
|
|||||||
name varchar(64),
|
name varchar(64),
|
||||||
url varchar(64),
|
url varchar(64),
|
||||||
username varchar(40),
|
username varchar(40),
|
||||||
password varchar(40),
|
password varchar(128),
|
||||||
/*
|
/*
|
||||||
target_type indicates the type of target registry,
|
target_type indicates the type of target registry,
|
||||||
0 means it's a harbor instance,
|
0 means it's a harbor instance,
|
||||||
|
@ -44,6 +44,10 @@ use_compressed_js = on
|
|||||||
#Maximum number of job workers in job service
|
#Maximum number of job workers in job service
|
||||||
max_job_workers = 3
|
max_job_workers = 3
|
||||||
|
|
||||||
|
#Secret key for encryption/decryption, its length has to be 16 chars
|
||||||
|
#**NOTE** if this changes, previously encrypted password will not be decrypted!
|
||||||
|
secret_key = secretkey1234567
|
||||||
|
|
||||||
#Determine whether the job service should verify the ssl cert when it connects to a remote registry.
|
#Determine whether the job service should verify the ssl cert when it connects to a remote registry.
|
||||||
#Set this flag to off when the remote registry uses a self-signed or untrusted certificate.
|
#Set this flag to off when the remote registry uses a self-signed or untrusted certificate.
|
||||||
verify_remote_cert = on
|
verify_remote_cert = on
|
||||||
|
@ -16,6 +16,10 @@ if sys.version_info[:3][0] == 3:
|
|||||||
import configparser as ConfigParser
|
import configparser as ConfigParser
|
||||||
import io as StringIO
|
import io as StringIO
|
||||||
|
|
||||||
|
def validate(conf):
|
||||||
|
if len(conf.get("configuration", "secret_key")) != 16:
|
||||||
|
raise Exception("Error: The length of secret key has to be 16 characters!")
|
||||||
|
|
||||||
#Read configurations
|
#Read configurations
|
||||||
conf = StringIO.StringIO()
|
conf = StringIO.StringIO()
|
||||||
conf.write("[configuration]\n")
|
conf.write("[configuration]\n")
|
||||||
@ -24,6 +28,8 @@ conf.seek(0, os.SEEK_SET)
|
|||||||
rcp = ConfigParser.RawConfigParser()
|
rcp = ConfigParser.RawConfigParser()
|
||||||
rcp.readfp(conf)
|
rcp.readfp(conf)
|
||||||
|
|
||||||
|
validate(rcp)
|
||||||
|
|
||||||
hostname = rcp.get("configuration", "hostname")
|
hostname = rcp.get("configuration", "hostname")
|
||||||
ui_url = rcp.get("configuration", "ui_url_protocol") + "://" + hostname
|
ui_url = rcp.get("configuration", "ui_url_protocol") + "://" + hostname
|
||||||
email_server = rcp.get("configuration", "email_server")
|
email_server = rcp.get("configuration", "email_server")
|
||||||
@ -49,6 +55,7 @@ crt_commonname = rcp.get("configuration", "crt_commonname")
|
|||||||
crt_email = rcp.get("configuration", "crt_email")
|
crt_email = rcp.get("configuration", "crt_email")
|
||||||
max_job_workers = rcp.get("configuration", "max_job_workers")
|
max_job_workers = rcp.get("configuration", "max_job_workers")
|
||||||
verify_remote_cert = rcp.get("configuration", "verify_remote_cert")
|
verify_remote_cert = rcp.get("configuration", "verify_remote_cert")
|
||||||
|
secret_key = rcp.get("configuration", "secret_key")
|
||||||
########
|
########
|
||||||
|
|
||||||
ui_secret = ''.join(random.choice(string.ascii_letters+string.digits) for i in range(16))
|
ui_secret = ''.join(random.choice(string.ascii_letters+string.digits) for i in range(16))
|
||||||
@ -101,6 +108,7 @@ render(os.path.join(templates_dir, "ui", "env"),
|
|||||||
self_registration=self_registration,
|
self_registration=self_registration,
|
||||||
use_compressed_js=use_compressed_js,
|
use_compressed_js=use_compressed_js,
|
||||||
ui_secret=ui_secret,
|
ui_secret=ui_secret,
|
||||||
|
secret_key=secret_key,
|
||||||
verify_remote_cert=verify_remote_cert)
|
verify_remote_cert=verify_remote_cert)
|
||||||
|
|
||||||
render(os.path.join(templates_dir, "ui", "app.conf"),
|
render(os.path.join(templates_dir, "ui", "app.conf"),
|
||||||
@ -126,6 +134,7 @@ render(os.path.join(templates_dir, "jobservice", "env"),
|
|||||||
db_password=db_password,
|
db_password=db_password,
|
||||||
ui_secret=ui_secret,
|
ui_secret=ui_secret,
|
||||||
max_job_workers=max_job_workers,
|
max_job_workers=max_job_workers,
|
||||||
|
secret_key=secret_key,
|
||||||
ui_url=ui_url,
|
ui_url=ui_url,
|
||||||
verify_remote_cert=verify_remote_cert)
|
verify_remote_cert=verify_remote_cert)
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@ MYSQL_PORT=3306
|
|||||||
MYSQL_USR=root
|
MYSQL_USR=root
|
||||||
MYSQL_PWD=$db_password
|
MYSQL_PWD=$db_password
|
||||||
UI_SECRET=$ui_secret
|
UI_SECRET=$ui_secret
|
||||||
|
SECRET_KEY=$secret_key
|
||||||
CONFIG_PATH=/etc/jobservice/app.conf
|
CONFIG_PATH=/etc/jobservice/app.conf
|
||||||
REGISTRY_URL=http://registry:5000
|
REGISTRY_URL=http://registry:5000
|
||||||
VERIFY_REMOTE_CERT=$verify_remote_cert
|
VERIFY_REMOTE_CERT=$verify_remote_cert
|
||||||
|
@ -12,6 +12,7 @@ AUTH_MODE=$auth_mode
|
|||||||
LDAP_URL=$ldap_url
|
LDAP_URL=$ldap_url
|
||||||
LDAP_BASE_DN=$ldap_basedn
|
LDAP_BASE_DN=$ldap_basedn
|
||||||
UI_SECRET=$ui_secret
|
UI_SECRET=$ui_secret
|
||||||
|
SECRET_KEY=$secret_key
|
||||||
SELF_REGISTRATION=$self_registration
|
SELF_REGISTRATION=$self_registration
|
||||||
USE_COMPRESSED_JS=$use_compressed_js
|
USE_COMPRESSED_JS=$use_compressed_js
|
||||||
LOG_LEVEL=debug
|
LOG_LEVEL=debug
|
||||||
|
@ -20,6 +20,7 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/vmware/harbor/dao"
|
"github.com/vmware/harbor/dao"
|
||||||
@ -34,10 +35,14 @@ import (
|
|||||||
// TargetAPI handles request to /api/targets/ping /api/targets/{}
|
// TargetAPI handles request to /api/targets/ping /api/targets/{}
|
||||||
type TargetAPI struct {
|
type TargetAPI struct {
|
||||||
BaseAPI
|
BaseAPI
|
||||||
|
secretKey string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prepare validates the user
|
// Prepare validates the user
|
||||||
func (t *TargetAPI) Prepare() {
|
func (t *TargetAPI) Prepare() {
|
||||||
|
//TODO:move to config
|
||||||
|
t.secretKey = os.Getenv("SECRET_KEY")
|
||||||
|
|
||||||
userID := t.ValidateUser()
|
userID := t.ValidateUser()
|
||||||
isSysAdmin, err := dao.IsAdminRole(userID)
|
isSysAdmin, err := dao.IsAdminRole(userID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -76,7 +81,7 @@ func (t *TargetAPI) Ping() {
|
|||||||
password = target.Password
|
password = target.Password
|
||||||
|
|
||||||
if len(password) != 0 {
|
if len(password) != 0 {
|
||||||
password, err = utils.ReversibleDecrypt(password)
|
password, err = utils.ReversibleDecrypt(password, t.secretKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("failed to decrypt password: %v", err)
|
log.Errorf("failed to decrypt password: %v", err)
|
||||||
t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
||||||
@ -136,7 +141,7 @@ func (t *TargetAPI) Get() {
|
|||||||
// modify other fields of target he does not need to input the password again.
|
// modify other fields of target he does not need to input the password again.
|
||||||
// The security issue can be fixed by enable https.
|
// The security issue can be fixed by enable https.
|
||||||
if len(target.Password) != 0 {
|
if len(target.Password) != 0 {
|
||||||
pwd, err := utils.ReversibleDecrypt(target.Password)
|
pwd, err := utils.ReversibleDecrypt(target.Password, t.secretKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("failed to decrypt password: %v", err)
|
log.Errorf("failed to decrypt password: %v", err)
|
||||||
t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
||||||
@ -162,7 +167,7 @@ func (t *TargetAPI) List() {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
str, err := utils.ReversibleDecrypt(target.Password)
|
str, err := utils.ReversibleDecrypt(target.Password, t.secretKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("failed to decrypt password: %v", err)
|
log.Errorf("failed to decrypt password: %v", err)
|
||||||
t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
||||||
@ -201,7 +206,11 @@ func (t *TargetAPI) Post() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(target.Password) != 0 {
|
if len(target.Password) != 0 {
|
||||||
target.Password = utils.ReversibleEncrypt(target.Password)
|
target.Password, err = utils.ReversibleEncrypt(target.Password, t.secretKey)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed to encrypt password: %v", err)
|
||||||
|
t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
id, err := dao.AddRepTarget(*target)
|
id, err := dao.AddRepTarget(*target)
|
||||||
@ -275,7 +284,11 @@ func (t *TargetAPI) Put() {
|
|||||||
target.ID = id
|
target.ID = id
|
||||||
|
|
||||||
if len(target.Password) != 0 {
|
if len(target.Password) != 0 {
|
||||||
target.Password = utils.ReversibleEncrypt(target.Password)
|
target.Password, err = utils.ReversibleEncrypt(target.Password, t.secretKey)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed to encrypt password: %v", err)
|
||||||
|
t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := dao.UpdateRepTarget(*target); err != nil {
|
if err := dao.UpdateRepTarget(*target); err != nil {
|
||||||
|
@ -31,6 +31,7 @@ var localUIURL string
|
|||||||
var localRegURL string
|
var localRegURL string
|
||||||
var logDir string
|
var logDir string
|
||||||
var uiSecret string
|
var uiSecret string
|
||||||
|
var secretKey string
|
||||||
var verifyRemoteCert string
|
var verifyRemoteCert string
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@ -86,6 +87,11 @@ func init() {
|
|||||||
beego.LoadAppConfig("ini", configPath)
|
beego.LoadAppConfig("ini", configPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
secretKey = os.Getenv("SECRET_KEY")
|
||||||
|
if len(secretKey) != 16 {
|
||||||
|
panic("The length of secretkey has to be 16 characters!")
|
||||||
|
}
|
||||||
|
|
||||||
log.Debugf("config: maxJobWorkers: %d", maxJobWorkers)
|
log.Debugf("config: maxJobWorkers: %d", maxJobWorkers)
|
||||||
log.Debugf("config: localUIURL: %s", localUIURL)
|
log.Debugf("config: localUIURL: %s", localUIURL)
|
||||||
log.Debugf("config: localRegURL: %s", localRegURL)
|
log.Debugf("config: localRegURL: %s", localRegURL)
|
||||||
@ -119,6 +125,11 @@ func UISecret() string {
|
|||||||
return uiSecret
|
return uiSecret
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SecretKey will return the secret key for encryption/decryption password in target.
|
||||||
|
func SecretKey() string {
|
||||||
|
return secretKey
|
||||||
|
}
|
||||||
|
|
||||||
// VerifyRemoteCert return the flag to tell jobservice whether or not verify the cert of remote registry
|
// VerifyRemoteCert return the flag to tell jobservice whether or not verify the cert of remote registry
|
||||||
func VerifyRemoteCert() bool {
|
func VerifyRemoteCert() bool {
|
||||||
return verifyRemoteCert != "off"
|
return verifyRemoteCert != "off"
|
||||||
|
@ -231,7 +231,7 @@ func (sm *SM) Reset(jid int64) error {
|
|||||||
pwd := target.Password
|
pwd := target.Password
|
||||||
|
|
||||||
if len(pwd) != 0 {
|
if len(pwd) != 0 {
|
||||||
pwd, err = uti.ReversibleDecrypt(pwd)
|
pwd, err = uti.ReversibleDecrypt(pwd, config.SecretKey())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to decrypt password: %v", err)
|
return fmt.Errorf("failed to decrypt password: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -16,9 +16,14 @@
|
|||||||
package utils
|
package utils
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/aes"
|
||||||
|
"crypto/cipher"
|
||||||
|
"crypto/rand"
|
||||||
"crypto/sha1"
|
"crypto/sha1"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
|
|
||||||
"golang.org/x/crypto/pbkdf2"
|
"golang.org/x/crypto/pbkdf2"
|
||||||
)
|
)
|
||||||
@ -28,13 +33,48 @@ func Encrypt(content string, salt string) string {
|
|||||||
return fmt.Sprintf("%x", pbkdf2.Key([]byte(content), []byte(salt), 4096, 16, sha1.New))
|
return fmt.Sprintf("%x", pbkdf2.Key([]byte(content), []byte(salt), 4096, 16, sha1.New))
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReversibleEncrypt encrypts the str with base64
|
// ReversibleEncrypt encrypts the str with aes/base64
|
||||||
func ReversibleEncrypt(str string) string {
|
func ReversibleEncrypt(str, key string) (string, error) {
|
||||||
return base64.StdEncoding.EncodeToString([]byte(str))
|
keyBytes := []byte(key)
|
||||||
|
var block cipher.Block
|
||||||
|
var err error
|
||||||
|
|
||||||
|
if block, err = aes.NewCipher(keyBytes); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
cipherText := make([]byte, aes.BlockSize+len(str))
|
||||||
|
iv := cipherText[:aes.BlockSize]
|
||||||
|
if _, err = io.ReadFull(rand.Reader, iv); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
cfb := cipher.NewCFBEncrypter(block, iv)
|
||||||
|
cfb.XORKeyStream(cipherText[aes.BlockSize:], []byte(str))
|
||||||
|
encrypted := base64.StdEncoding.EncodeToString(cipherText)
|
||||||
|
return encrypted, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReversibleDecrypt decrypts the str with base64
|
// ReversibleDecrypt decrypts the str with aes/base64
|
||||||
func ReversibleDecrypt(str string) (string, error) {
|
func ReversibleDecrypt(str, key string) (string, error) {
|
||||||
b, err := base64.StdEncoding.DecodeString(str)
|
keyBytes := []byte(key)
|
||||||
return string(b), err
|
var block cipher.Block
|
||||||
|
var cipherText []byte
|
||||||
|
var err error
|
||||||
|
|
||||||
|
if block, err = aes.NewCipher(keyBytes); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if cipherText, err = base64.StdEncoding.DecodeString(str); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if len(cipherText) < aes.BlockSize {
|
||||||
|
err = errors.New("cipherText too short")
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
iv := cipherText[:aes.BlockSize]
|
||||||
|
cipherText = cipherText[aes.BlockSize:]
|
||||||
|
cfb := cipher.NewCFBDecrypter(block, iv)
|
||||||
|
cfb.XORKeyStream(cipherText, cipherText)
|
||||||
|
return string(cipherText), nil
|
||||||
}
|
}
|
||||||
|
@ -61,3 +61,23 @@ func TestParseRepository(t *testing.T) {
|
|||||||
t.Errorf("unexpected rest: [%s] != [%s]", rest, "")
|
t.Errorf("unexpected rest: [%s] != [%s]", rest, "")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestReversibleEncrypt(t *testing.T) {
|
||||||
|
password := "password"
|
||||||
|
key := "1234567890123456"
|
||||||
|
encrypted, err := ReversibleEncrypt(password, key)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Failed to encrypt: %v", err)
|
||||||
|
}
|
||||||
|
t.Logf("Encrypted password: %s", encrypted)
|
||||||
|
if encrypted == password {
|
||||||
|
t.Errorf("Encrypted password is identical to the original")
|
||||||
|
}
|
||||||
|
decrypted, err := ReversibleDecrypt(encrypted, key)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Failed to decrypt: %v", err)
|
||||||
|
}
|
||||||
|
if decrypted != password {
|
||||||
|
t.Errorf("decrypted password: %s, is not identical to original", decrypted)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user