encrypt passwords and secret

This commit is contained in:
Wenkai Yin 2017-02-15 15:25:59 +08:00
parent 06519bb3f2
commit 390f89ee0a
21 changed files with 441 additions and 66 deletions

View File

@ -25,9 +25,9 @@ env:
HARBOR_ADMIN_PASSWD: Harbor12345
UI_SECRET: tempString
MAX_JOB_WORKERS: 3
SECRET_KEY: 1234567890123456
AUTH_MODE: db_auth
SELF_REGISTRATION: on
KEY_PATH: /data/secretkey
before_install:
- sudo ./tests/hostcfg.sh

View File

@ -49,6 +49,7 @@ services:
restart: always
volumes:
- /data/config/:/etc/harbor/
- /data/secretkey:/harbor/secretkey
depends_on:
- log
logging:
@ -66,6 +67,7 @@ services:
volumes:
- ../common/config/ui/app.conf:/etc/ui/app.conf
- ../common/config/ui/private_key.pem:/etc/ui/private_key.pem
- /data/secretkey:/harbor/secretkey
depends_on:
- log
- adminserver
@ -85,6 +87,7 @@ services:
volumes:
- /data/job_logs:/var/log/jobs
- ../common/config/jobservice/app.conf:/etc/jobservice/app.conf
- /data/secretkey:/harbor/secretkey
depends_on:
- ui
- adminserver

View File

@ -49,6 +49,7 @@ services:
restart: always
volumes:
- /data/config/:/etc/harbor/
- /data/secretkey:/harbor/secretkey
depends_on:
- log
logging:
@ -66,6 +67,7 @@ services:
- ./common/config/ui/app.conf:/etc/ui/app.conf
- ./common/config/ui/private_key.pem:/etc/ui/private_key.pem
- /data:/harbor_storage
- /data/secretkey:/harbor/secretkey
depends_on:
- log
- adminserver
@ -84,6 +86,7 @@ services:
volumes:
- /data/job_logs:/var/log/jobs
- ./common/config/jobservice/app.conf:/etc/jobservice/app.conf
- /data/secretkey:/harbor/secretkey
depends_on:
- ui
- adminserver

View File

@ -10,6 +10,9 @@ import argparse
import subprocess
import shutil
from io import open
import base64
from Crypto import Random
from Crypto.Cipher import AES
if sys.version_info[:3][0] == 2:
import ConfigParser as ConfigParser
@ -54,6 +57,11 @@ def get_secret_key(path):
print("generated and saved secret key")
return key
def aesEncrypt(key, raw):
iv = Random.new().read(AES.block_size)
cipher = AES.new(key, AES.MODE_CFB, iv)
return base64.b64encode(iv + cipher.encrypt(raw))
base_dir = os.path.dirname(__file__)
config_dir = os.path.join(base_dir, "common/config")
templates_dir = os.path.join(base_dir, "common/templates")
@ -125,6 +133,7 @@ secret_key = get_secret_key(secretkey_path)
########
ui_secret = ''.join(random.choice(string.ascii_letters+string.digits) for i in range(16))
#ui_secret = aesEncrypt(secret_key, ui_secret_raw)
adminserver_config_dir = os.path.join(config_dir,"adminserver")
if not os.path.exists(adminserver_config_dir):

View File

@ -19,14 +19,13 @@ import (
"encoding/json"
"io/ioutil"
"net/http"
"os"
"github.com/vmware/harbor/src/adminserver/config"
cfg "github.com/vmware/harbor/src/adminserver/systemcfg"
"github.com/vmware/harbor/src/common/utils/log"
)
func isAuthenticated(r *http.Request) (bool, error) {
secret := os.Getenv("UI_SECRET")
c, err := r.Cookie("secret")
if err != nil {
if err == http.ErrNoCookie {
@ -34,7 +33,7 @@ func isAuthenticated(r *http.Request) (bool, error) {
}
return false, err
}
return c != nil && c.Value == secret, nil
return c != nil && c.Value == config.Secret(), nil
}
// ListCfgs lists configurations

View File

@ -25,24 +25,48 @@ import (
"os"
"testing"
"github.com/vmware/harbor/src/adminserver/config"
"github.com/vmware/harbor/src/adminserver/systemcfg"
"github.com/vmware/harbor/src/common/config"
comcfg "github.com/vmware/harbor/src/common/config"
"github.com/vmware/harbor/src/common/utils/test"
)
func TestConfigAPI(t *testing.T) {
path := "/tmp/config.json"
secret := "secret"
configPath := "/tmp/config.json"
secretKeyPath := "/tmp/secretkey"
_, err := test.GenerateKey(secretKeyPath)
if err != nil {
t.Errorf("failed to generate secret key: %v", err)
return
}
defer os.Remove(secretKeyPath)
secret := "secret"
/*
secretPlaintext := "secret"
secretCiphertext, err := utils.ReversibleEncrypt(secretPlaintext, string(data))
if err != nil {
t.Errorf("failed to encrypt secret: %v", err)
return
}
*/
envs := map[string]string{
"JSON_STORE_PATH": path,
"UI_SECRET": secret,
"MYSQL_PORT": "3306",
"TOKEN_EXPIRATION": "30",
"CFG_EXPIRATION": "5",
"MAX_JOB_WORKERS": "3",
"LDAP_SCOPE": "3",
"LDAP_TIMEOUT": "30",
"EMAIL_PORT": "25",
"JSON_STORE_PATH": configPath,
"KEY_PATH": secretKeyPath,
"UI_SECRET": secret,
"MYSQL_PORT": "3306",
"TOKEN_EXPIRATION": "30",
"CFG_EXPIRATION": "5",
"MAX_JOB_WORKERS": "3",
"LDAP_SCOPE": "3",
"LDAP_TIMEOUT": "30",
"EMAIL_PORT": "25",
"MYSQL_PWD": "",
"LDAP_SEARCH_PWD": "",
"EMAIL_PWD": "",
"HARBOR_ADMIN_PASSWORD": "",
}
for k, v := range envs {
@ -50,10 +74,15 @@ func TestConfigAPI(t *testing.T) {
t.Fatalf("failed to set env %s: %v", k, err)
}
}
defer os.Remove(path)
defer os.Remove(configPath)
if err := config.Init(); err != nil {
t.Errorf("failed to load configurations of adminserver: %v", err)
return
}
if err := systemcfg.Init(); err != nil {
t.Errorf("failed to initialize systemconfigurations: %v", err)
t.Errorf("failed to initialize system configurations: %v", err)
return
}
@ -88,7 +117,7 @@ func TestConfigAPI(t *testing.T) {
return
}
scope := int(m[config.LDAPScope].(float64))
scope := int(m[comcfg.LDAPScope].(float64))
if scope != 3 {
t.Errorf("unexpected ldap scope: %d != %d", scope, 3)
return
@ -96,7 +125,7 @@ func TestConfigAPI(t *testing.T) {
// modify configurations
c := map[string]interface{}{
config.AUTHMode: config.LDAPAuth,
comcfg.AUTHMode: comcfg.LDAPAuth,
}
b, err := json.Marshal(c)
@ -146,9 +175,9 @@ func TestConfigAPI(t *testing.T) {
return
}
mode := m[config.AUTHMode].(string)
if mode != config.LDAPAuth {
t.Errorf("unexpected ldap scope: %s != %s", mode, config.LDAPAuth)
mode := m[comcfg.AUTHMode].(string)
if mode != comcfg.LDAPAuth {
t.Errorf("unexpected ldap scope: %s != %s", mode, comcfg.LDAPAuth)
return
}
}

View File

@ -0,0 +1,66 @@
/*
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 config
import (
"io/ioutil"
"os"
//"github.com/vmware/harbor/src/common/utils"
)
const defaultKeyPath string = "/harbor/secretkey"
var (
secret string
secretKey string
)
// Init configurations used by adminserver
func Init() error {
path := os.Getenv("KEY_PATH")
if len(path) == 0 {
path = defaultKeyPath
}
b, err := ioutil.ReadFile(path)
if err != nil {
return err
}
secretKey = string(b)
secret = os.Getenv("UI_SECRET")
/*
secretCipherText := os.Getenv("UI_SECRET")
secret, err = utils.ReversibleDecrypt(secretCipherText, secretKey)
if err != nil {
return err
}
*/
return nil
}
// Secret is used by API to authenticate requests
func Secret() string {
return secret
}
// SecretKey is used to encrypt or decrypt
func SecretKey() string {
return secretKey
}

View File

@ -0,0 +1,67 @@
/*
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 config
import (
"os"
"testing"
"github.com/vmware/harbor/src/common/utils/test"
)
func TestConfig(t *testing.T) {
secretKeyPath := "/tmp/secretkey"
secretKey, err := test.GenerateKey(secretKeyPath)
if err != nil {
t.Errorf("failed to generate secret key: %v", err)
return
}
defer os.Remove(secretKeyPath)
secret := "secret"
/*
secretPlaintext := "secret"
secretCiphertext, err := utils.ReversibleEncrypt(secretPlaintext, string(data))
if err != nil {
t.Errorf("failed to encrypt secret: %v", err)
return
}
*/
envs := map[string]string{
"KEY_PATH": secretKeyPath,
"UI_SECRET": secret,
}
for k, v := range envs {
if err := os.Setenv(k, v); err != nil {
t.Fatalf("failed to set env %s: %v", k, err)
}
}
if err := Init(); err != nil {
t.Errorf("failed to load configurations of adminserver: %v", err)
return
}
if SecretKey() != secretKey {
t.Errorf("unexpected secret key: %s != %s", SecretKey(), secretKey)
}
if Secret() != secret {
t.Errorf("unexpected secret: %s != %s", Secret(), secret)
}
}

View File

@ -19,7 +19,8 @@ import (
"net/http"
"os"
cfg "github.com/vmware/harbor/src/adminserver/systemcfg"
"github.com/vmware/harbor/src/adminserver/config"
syscfg "github.com/vmware/harbor/src/adminserver/systemcfg"
"github.com/vmware/harbor/src/common/utils/log"
)
@ -40,8 +41,15 @@ func (s *Server) Serve() error {
}
func main() {
log.Info("loading configurations of adminserver...")
if err := config.Init(); err != nil {
log.Fatalf("failed to load configurations of adminserver: %v", err)
}
log.Info("load completed")
log.Info("initializing system configurations...")
if err := cfg.Init(); err != nil {
if err := syscfg.Init(); err != nil {
log.Fatalf("failed to initialize the system: %v", err)
}
log.Info("system initialization completed")

View File

@ -20,12 +20,22 @@ import (
"os"
"strconv"
"github.com/vmware/harbor/src/adminserver/config"
"github.com/vmware/harbor/src/adminserver/systemcfg/store"
"github.com/vmware/harbor/src/adminserver/systemcfg/store/json"
comcfg "github.com/vmware/harbor/src/common/config"
"github.com/vmware/harbor/src/common/utils"
"github.com/vmware/harbor/src/common/utils/log"
)
// Keys need to be encrypted or decrypted
var Keys = []string{
comcfg.EmailPassword,
comcfg.LDAPSearchPwd,
comcfg.MySQLPassword,
comcfg.AdminInitialPassword,
}
var cfgStore store.Driver
// Init system configurations. Read from config store first, if null read from env
@ -43,7 +53,7 @@ func Init() (err error) {
}
log.Infof("configuration store driver: %s", cfgStore.Name())
cfg, err := cfgStore.Read()
cfg, err := GetSystemCfg()
if err != nil {
return err
}
@ -61,7 +71,7 @@ func Init() (err error) {
}
//sync configurations into cfg store
if err = cfgStore.Write(cfg); err != nil {
if err = UpdateSystemCfg(cfg); err != nil {
return err
}
@ -102,7 +112,6 @@ func readFromEnv(cfg map[string]interface{}) error {
cfg[comcfg.JobLogDir] = os.Getenv("LOG_DIR")
//TODO remove
cfg[comcfg.UseCompressedJS] = os.Getenv("USE_COMPRESSED_JS") == "on"
cfg[comcfg.SecretKey] = os.Getenv("SECRET_KEY")
cfgExpi, err := strconv.Atoi(os.Getenv("CFG_EXPIRATION"))
if err != nil {
return err
@ -162,10 +171,64 @@ func initFromEnv() (map[string]interface{}, error) {
// GetSystemCfg returns the system configurations
func GetSystemCfg() (map[string]interface{}, error) {
return cfgStore.Read()
m, err := cfgStore.Read()
if err != nil {
return nil, err
}
if err = decrypt(m, Keys, config.SecretKey()); err != nil {
return nil, err
}
return m, nil
}
// UpdateSystemCfg updates the system configurations
func UpdateSystemCfg(cfg map[string]interface{}) error {
if err := encrypt(cfg, Keys, config.SecretKey()); err != nil {
return err
}
return cfgStore.Write(cfg)
}
func encrypt(m map[string]interface{}, keys []string, secretKey string) error {
for _, key := range keys {
v, ok := m[key]
if !ok {
continue
}
if len(v.(string)) == 0 {
continue
}
cipherText, err := utils.ReversibleEncrypt(v.(string), secretKey)
if err != nil {
return err
}
m[key] = cipherText
}
return nil
}
func decrypt(m map[string]interface{}, keys []string, secretKey string) error {
for _, key := range keys {
v, ok := m[key]
if !ok {
continue
}
if len(v.(string)) == 0 {
continue
}
text, err := utils.ReversibleDecrypt(v.(string), secretKey)
if err != nil {
return err
}
m[key] = text
}
return nil
}

View File

@ -39,14 +39,18 @@ func TestSystemcfg(t *testing.T) {
}
m := map[string]string{
"AUTH_MODE": comcfg.DBAuth,
"LDAP_SCOPE": "1",
"LDAP_TIMEOUT": "30",
"MYSQL_PORT": "3306",
"MAX_JOB_WORKERS": "3",
"TOKEN_EXPIRATION": "30",
"CFG_EXPIRATION": "5",
"EMAIL_PORT": "25",
"AUTH_MODE": comcfg.DBAuth,
"LDAP_SCOPE": "1",
"LDAP_TIMEOUT": "30",
"MYSQL_PORT": "3306",
"MAX_JOB_WORKERS": "3",
"TOKEN_EXPIRATION": "30",
"CFG_EXPIRATION": "5",
"EMAIL_PORT": "25",
"MYSQL_PWD": "",
"LDAP_SEARCH_PWD": "",
"EMAIL_PWD": "",
"HARBOR_ADMIN_PASSWORD": "",
}
for k, v := range m {

View File

@ -74,7 +74,6 @@ const (
CfgExpiration = "cfg_expiration"
JobLogDir = "job_log_dir"
UseCompressedJS = "use_compressed_js"
SecretKey = "secret_key"
AdminInitialPassword = "admin_initial_password"
)

View File

@ -24,7 +24,7 @@ import (
)
var adminServerDefaultConfig = map[string]interface{}{
config.ExtEndpoint: "host01.com",
config.ExtEndpoint: "host01.com",
config.AUTHMode: config.DBAuth,
config.DatabaseType: "mysql",
config.MySQLHost: "127.0.0.1",
@ -58,7 +58,6 @@ var adminServerDefaultConfig = map[string]interface{}{
config.CfgExpiration: 5,
config.JobLogDir: "/var/log/jobs",
config.UseCompressedJS: true,
config.SecretKey: "secret",
config.AdminInitialPassword: "password",
}

View File

@ -0,0 +1,41 @@
/*
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 test
import (
"crypto/aes"
"crypto/rand"
"fmt"
"io/ioutil"
)
// GenerateKey generates aes key
func GenerateKey(path string) (string, error) {
data := make([]byte, aes.BlockSize)
n, err := rand.Read(data)
if err != nil {
return "", fmt.Errorf("failed to generate random bytes: %v", err)
}
if n != aes.BlockSize {
return "", fmt.Errorf("the length of random bytes %d != %d", n, aes.BlockSize)
}
if err = ioutil.WriteFile(path, data, 0777); err != nil {
return "", fmt.Errorf("failed write secret key to file %s: %v", path, err)
}
return string(data), nil
}

View File

@ -16,16 +16,55 @@
package config
import (
"io/ioutil"
"os"
comcfg "github.com/vmware/harbor/src/common/config"
"github.com/vmware/harbor/src/common/models"
//"github.com/vmware/harbor/src/common/utils"
//"github.com/vmware/harbor/src/common/utils/log"
)
const defaultKeyPath string = "/harbor/secretkey"
var mg *comcfg.Manager
var (
secret string
secretKey string
)
func initSecretAndKey() error {
path := os.Getenv("KEY_PATH")
if len(path) == 0 {
path = defaultKeyPath
}
b, err := ioutil.ReadFile(path)
if err != nil {
return err
}
secretKey = string(b)
secret = os.Getenv("UI_SECRET")
/*
secretCipherText := os.Getenv("UI_SECRET")
secret, err = utils.ReversibleDecrypt(secretCipherText, secretKey)
if err != nil {
log.Errorf("failed to decrypt secret: %v", err)
}
*/
return nil
}
// Init configurations
func Init() error {
if err := initSecretAndKey(); err != nil {
return err
}
adminServerURL := os.Getenv("ADMIN_SERVER_URL")
if len(adminServerURL) == 0 {
adminServerURL = "http://adminserver"
@ -108,17 +147,13 @@ func LogDir() (string, error) {
// SecretKey will return the secret key for encryption/decryption password in target.
func SecretKey() (string, error) {
cfg, err := mg.Get()
if err != nil {
return "", err
}
return cfg[comcfg.SecretKey].(string), nil
return secretKey, nil
}
// UISecret returns the value of UI secret cookie, used for communication between UI and JobService
// TODO
// UISecret returns a secret used for communication of UI, JobService
// and Adminserver
func UISecret() string {
return os.Getenv("UI_SECRET")
return secret
}
// ExtEndpoint ...

View File

@ -30,13 +30,22 @@ func TestConfig(t *testing.T) {
}
defer server.Close()
url := os.Getenv("ADMIN_SERVER_URL")
defer os.Setenv("ADMIN_SERVER_URL", url)
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 := Init(); err != nil {
t.Fatalf("failed to initialize configurations: %v", err)
}

View File

@ -215,8 +215,6 @@ func validateCfg(c map[string]string) (bool, error) {
}
}
log.Infof("===========%v", c)
if ldapURL, ok := c[comcfg.LDAPURL]; ok && len(ldapURL) == 0 {
return isSysErr, fmt.Errorf("%s is empty", comcfg.LDAPURL)
}
@ -325,8 +323,7 @@ func convertForGet(cfg map[string]interface{}) (map[string]*value, error) {
comcfg.AdminInitialPassword,
comcfg.EmailPassword,
comcfg.LDAPSearchPwd,
comcfg.MySQLPassword,
comcfg.SecretKey}
comcfg.MySQLPassword}
for _, del := range dels {
if _, ok := cfg[del]; ok {
delete(cfg, del)

View File

@ -17,17 +17,53 @@ package config
import (
"encoding/json"
"io/ioutil"
"os"
comcfg "github.com/vmware/harbor/src/common/config"
"github.com/vmware/harbor/src/common/models"
//"github.com/vmware/harbor/src/common/utils"
"github.com/vmware/harbor/src/common/utils/log"
)
const defaultKeyPath string = "/harbor/secretkey"
var mg *comcfg.Manager
var (
secret string
secretKey string
)
func initSecretAndKey() error {
path := os.Getenv("KEY_PATH")
if len(path) == 0 {
path = defaultKeyPath
}
b, err := ioutil.ReadFile(path)
if err != nil {
return err
}
secretKey = string(b)
secret = os.Getenv("UI_SECRET")
/*
secretCipherText := os.Getenv("UI_SECRET")
secret, err = utils.ReversibleDecrypt(secretCipherText, secretKey)
if err != nil {
return err
}
*/
return nil
}
// Init configurations
func Init() error {
if err := initSecretAndKey(); err != nil {
return err
}
adminServerURL := os.Getenv("ADMIN_SERVER_URL")
if len(adminServerURL) == 0 {
adminServerURL = "http://adminserver"
@ -125,11 +161,7 @@ func ExtEndpoint() (string, error) {
// SecretKey returns the secret key to encrypt the password of target
func SecretKey() (string, error) {
cfg, err := mg.Get()
if err != nil {
return "", err
}
return cfg[comcfg.SecretKey].(string), nil
return secretKey, nil
}
// SelfRegistration returns the enablement of self registration
@ -228,8 +260,8 @@ func Database() (*models.Database, error) {
return database, nil
}
// UISecret returns the value of UI secret cookie, used for communication between UI and JobService
// TODO
// UISecret returns a secret used for communication of UI, JobService
// and Adminserver
func UISecret() string {
return os.Getenv("UI_SECRET")
return secret
}

View File

@ -29,13 +29,22 @@ func TestConfig(t *testing.T) {
}
defer server.Close()
url := os.Getenv("ADMIN_SERVER_URL")
defer os.Setenv("ADMIN_SERVER_URL", url)
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 := Init(); err != nil {
t.Fatalf("failed to initialize configurations: %v", err)
}

View File

@ -30,6 +30,7 @@ services:
restart: always
volumes:
- /data/config/:/etc/harbor/
- /data/secretkey:/harbor/secretkey
ports:
- 8888:80
ldap:

View File

@ -11,3 +11,5 @@ cp make/common/config/ui/app.conf conf/.
sed -i -r "s/MYSQL_HOST=mysql/MYSQL_HOST=127.0.0.1/" make/common/config/adminserver/env
sed -i -r "s|REGISTRY_URL=http://registry:5000|REGISTRY_URL=http://127.0.0.1:5000|" make/common/config/adminserver/env
sed -i -r "s/UI_SECRET=.*/UI_SECRET=$UI_SECRET/" make/common/config/adminserver/env
chmod 777 /data/