Move certificate verification to target level

The certificate verification is on system level before this commit. Moving it
to target level makes the configuration more flexible for different targets.
This commit is contained in:
Wenkai Yin 2017-09-22 15:08:10 +08:00
parent 75af80b4e8
commit 2156750b04
35 changed files with 82 additions and 96 deletions

View File

@ -2339,6 +2339,9 @@ definitions:
type: integer
format: int
description: Reserved field.
insecure:
type: boolean
description: Whether or not the certificate will be verified when Harbor tries to access the server.
creation_time:
type: string
description: The create time of the policy.
@ -2360,6 +2363,9 @@ definitions:
password:
type: string
description: The target server password.
insecure:
type: boolean
description: Whether or not the certificate will be verified when Harbor tries to access the server.
PingTarget:
type: object
properties:
@ -2372,6 +2378,9 @@ definitions:
password:
type: string
description: The target server password.
insecure:
type: boolean
description: Whether or not the certificate will be verified when Harbor tries to access the server.
PutTarget:
type: object
properties:
@ -2387,6 +2396,9 @@ definitions:
password:
type: string
description: The target server password.
insecure:
type: boolean
description: Whether or not the certificate will be verified when Harbor tries to access the server.
HasAdminRole:
type: object
properties:

View File

@ -163,6 +163,7 @@ create table replication_target (
1 means it's a regulart registry
*/
target_type tinyint(1) NOT NULL DEFAULT 0,
insecure tinyint(1) NOT NULL DEFAULT 0,
creation_time timestamp default CURRENT_TIMESTAMP,
update_time timestamp default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP,
PRIMARY KEY (id)

View File

@ -158,6 +158,7 @@ create table replication_target (
1 means it's a regulart registry
*/
target_type tinyint(1) NOT NULL DEFAULT 0,
insecure tinyint(1) NOT NULL DEFAULT 0,
creation_time timestamp default CURRENT_TIMESTAMP,
update_time timestamp default CURRENT_TIMESTAMP
);

View File

@ -27,7 +27,6 @@ EMAIL_FROM=$email_from
EMAIL_IDENTITY=$email_identity
HARBOR_ADMIN_PASSWORD=$harbor_admin_password
PROJECT_CREATION_RESTRICTION=$project_creation_restriction
VERIFY_REMOTE_CERT=$verify_remote_cert
MAX_JOB_WORKERS=$max_job_workers
UI_SECRET=$ui_secret
JOBSERVICE_SECRET=$jobservice_secret

View File

@ -149,7 +149,6 @@ if protocol == "https":
customize_crt = rcp.get("configuration", "customize_crt")
max_job_workers = rcp.get("configuration", "max_job_workers")
token_expiration = rcp.get("configuration", "token_expiration")
verify_remote_cert = rcp.get("configuration", "verify_remote_cert")
proj_cre_restriction = rcp.get("configuration", "project_creation_restriction")
secretkey_path = rcp.get("configuration", "secretkey_path")
if rcp.has_option("configuration", "admiral_url"):
@ -239,7 +238,6 @@ render(os.path.join(templates_dir, "adminserver", "env"),
email_identity=email_identity,
harbor_admin_password=harbor_admin_password,
project_creation_restriction=proj_cre_restriction,
verify_remote_cert=verify_remote_cert,
max_job_workers=max_job_workers,
ui_secret=ui_secret,
jobservice_secret=jobservice_secret,

View File

@ -111,10 +111,6 @@ var (
env: "MAX_JOB_WORKERS",
parse: parseStringToInt,
},
common.VerifyRemoteCert: &parser{
env: "VERIFY_REMOTE_CERT",
parse: parseStringToBool,
},
common.ProjectCreationRestriction: "PROJECT_CREATION_RESTRICTION",
common.AdminInitialPassword: "HARBOR_ADMIN_PASSWORD",
common.AdmiralEndpoint: "ADMIRAL_URL",

View File

@ -57,7 +57,6 @@ const (
EmailIdentity = "email_identity"
EmailInsecure = "email_insecure"
ProjectCreationRestriction = "project_creation_restriction"
VerifyRemoteCert = "verify_remote_cert"
MaxJobWorkers = "max_job_workers"
TokenExpiration = "token_expiration"
CfgExpiration = "cfg_expiration"

View File

@ -76,7 +76,7 @@ func DeleteRepTarget(id int64) error {
func UpdateRepTarget(target models.RepTarget) error {
o := GetOrmer()
target.UpdateTime = time.Now()
_, err := o.Update(&target, "URL", "Name", "Username", "Password", "UpdateTime")
_, err := o.Update(&target, "URL", "Name", "Username", "Password", "Insecure", "UpdateTime")
return err
}

View File

@ -105,6 +105,7 @@ type RepTarget struct {
Username string `orm:"column(username)" json:"username"`
Password string `orm:"column(password)" json:"password"`
Type int `orm:"column(target_type)" json:"type"`
Insecure bool `orm:"column(insecure)" json:"insecure"`
CreationTime time.Time `orm:"column(creation_time);auto_now_add" json:"creation_time"`
UpdateTime time.Time `orm:"column(update_time);auto_now" json:"update_time"`
}

View File

@ -53,7 +53,6 @@ var adminServerDefaultConfig = map[string]interface{}{
common.EmailInsecure: false,
common.EmailIdentity: "",
common.ProjectCreationRestriction: common.ProCrtRestrAdmOnly,
common.VerifyRemoteCert: false,
common.MaxJobWorkers: 3,
common.TokenExpiration: 30,
common.CfgExpiration: 5,

View File

@ -74,15 +74,6 @@ func initKeyProvider() {
keyProvider = comcfg.NewFileKeyProvider(path)
}
// VerifyRemoteCert returns bool value.
func VerifyRemoteCert() (bool, error) {
cfg, err := mg.Get()
if err != nil {
return true, err
}
return cfg[common.VerifyRemoteCert].(bool), nil
}
// Database ...
func Database() (*models.Database, error) {
cfg, err := mg.Get()

View File

@ -49,10 +49,6 @@ func TestConfig(t *testing.T) {
t.Fatalf("failed to initialize configurations: %v", err)
}
if _, err := VerifyRemoteCert(); err != nil {
t.Fatalf("failed to get verify remote cert: %v", err)
}
if _, err := Database(); err != nil {
t.Fatalf("failed to get database settings: %v", err)
}

View File

@ -106,7 +106,7 @@ func TestRepJob(t *testing.T) {
j, err := dao.GetRepJob(repJobID)
assert.Equal(models.JobRetrying, j.Status)
assert.Equal(1, rj.parm.Enabled)
assert.True(rj.parm.Insecure)
assert.False(rj.parm.Insecure)
rj2 := NewRepJob(99999)
err = rj2.Init()
assert.NotNil(err)

View File

@ -120,17 +120,12 @@ func (rj *RepJob) Init() error {
if err != nil {
return err
}
verify, err := config.VerifyRemoteCert()
if err != nil {
return err
}
rj.parm = &RepJobParm{
LocalRegURL: regURL,
Repository: job.Repository,
Tags: job.TagList,
Enabled: policy.Enabled,
Operation: job.Operation,
Insecure: !verify,
}
if policy.Enabled == 0 {
//worker will cancel this job
@ -159,6 +154,7 @@ func (rj *RepJob) Init() error {
}
rj.parm.TargetPassword = pwd
rj.parm.Insecure = target.Insecure
return nil
}

View File

@ -49,7 +49,6 @@ var (
common.EmailIdentity,
common.EmailInsecure,
common.ProjectCreationRestriction,
common.VerifyRemoteCert,
common.TokenExpiration,
common.ScanAllPolicy,
}
@ -81,7 +80,6 @@ var (
common.EmailSSL,
common.EmailInsecure,
common.SelfRegistration,
common.VerifyRemoteCert,
}
passwordKeys = []string{

View File

@ -61,7 +61,7 @@ func TestPutConfig(t *testing.T) {
apiTest := newHarborAPI()
cfg := map[string]interface{}{
common.VerifyRemoteCert: false,
common.TokenExpiration: 60,
}
code, err := apiTest.PutConfig(*admin, cfg)
@ -104,13 +104,13 @@ func TestResetConfig(t *testing.T) {
return
}
value, ok := cfgs[common.VerifyRemoteCert]
value, ok := cfgs[common.TokenExpiration]
if !ok {
t.Errorf("%s not found", common.VerifyRemoteCert)
t.Errorf("%s not found", common.TokenExpiration)
return
}
assert.Equal(value.Value.(bool), true, "unexpected value")
assert.Equal(int(value.Value.(float64)), 30, "unexpected 30")
ccc, err := config.GetSystemCfg()
if err != nil {

View File

@ -58,13 +58,8 @@ func (t *TargetAPI) Prepare() {
}
}
func (t *TargetAPI) ping(endpoint, username, password string) {
verify, err := config.VerifyRemoteCert()
if err != nil {
log.Errorf("failed to check whether insecure or not: %v", err)
t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
}
registry, err := newRegistryClient(endpoint, !verify, username, password)
func (t *TargetAPI) ping(endpoint, username, password string, insecure bool) {
registry, err := newRegistryClient(endpoint, insecure, username, password)
if err != nil {
// timeout, dns resolve error, connection refused, etc.
if urlErr, ok := err.(*url.Error); ok {
@ -105,6 +100,7 @@ func (t *TargetAPI) PingByID() {
endpoint := target.URL
username := target.Username
password := target.Password
insecure := target.Insecure
if len(password) != 0 {
password, err = utils.ReversibleDecrypt(password, t.secretKey)
if err != nil {
@ -112,7 +108,7 @@ func (t *TargetAPI) PingByID() {
t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
}
}
t.ping(endpoint, username, password)
t.ping(endpoint, username, password, insecure)
}
// Ping validates whether the target is reachable and whether the credential is valid
@ -121,6 +117,7 @@ func (t *TargetAPI) Ping() {
Endpoint string `json:"endpoint"`
Username string `json:"username"`
Password string `json:"password"`
Insecure bool `json:"insecure"`
}{}
t.DecodeJSONReq(&req)
@ -128,7 +125,7 @@ func (t *TargetAPI) Ping() {
t.CustomAbort(http.StatusBadRequest, "endpoint is required")
}
t.ping(req.Endpoint, req.Username, req.Password)
t.ping(req.Endpoint, req.Username, req.Password, req.Insecure)
}
// Get ...
@ -255,6 +252,7 @@ func (t *TargetAPI) Put() {
Endpoint *string `json:"endpoint"`
Username *string `json:"username"`
Password *string `json:"password"`
Insecure *bool `json:"insecure"`
}{}
t.DecodeJSONReq(&req)
@ -273,6 +271,9 @@ func (t *TargetAPI) Put() {
if req.Password != nil {
target.Password = *req.Password
}
if req.Insecure != nil {
target.Insecure = *req.Insecure
}
t.Validate(target)

View File

@ -276,15 +276,6 @@ func OnlyAdminCreateProject() (bool, error) {
return cfg[common.ProjectCreationRestriction].(string) == common.ProCrtRestrAdmOnly, nil
}
// VerifyRemoteCert returns bool value.
func VerifyRemoteCert() (bool, error) {
cfg, err := mg.Get()
if err != nil {
return true, err
}
return cfg[common.VerifyRemoteCert].(bool), nil
}
// Email returns email server settings
func Email() (*models.Email, error) {
cfg, err := mg.Get()

View File

@ -108,10 +108,6 @@ func TestConfig(t *testing.T) {
t.Fatalf("failed to get onldy admin create project: %v", err)
}
if _, err := VerifyRemoteCert(); err != nil {
t.Fatalf("failed to get verify remote cert: %v", err)
}
if _, err := Email(); err != nil {
t.Fatalf("failed to get email settings: %v", err)
}

View File

@ -38,6 +38,10 @@ export const CREATE_EDIT_ENDPOINT_TEMPLATE: string = `
<label for="destination_password" class="col-md-4 form-group-label-override">{{ 'DESTINATION.PASSWORD' | translate }}</label>
<input type="password" class="col-md-8" id="destination_password" [disabled]="testOngoing" [readonly]="!editable" [(ngModel)]="target.password" size="20" name="password" #password="ngModel" (focus)="clearPassword($event)">
</div>
<div class="form-group">
<label for="destination_insecure" class="col-md-4 form-group-label-override">{{'CONFIG.VERIFY_REMOTE_CERT' | translate }}</label>
<clr-checkbox #insecure class="col-md-8" name="insecure" id="destination_insecure" [(ngModel)]="target.insecure"></clr-checkbox>
</div>
<div class="form-group">
<label for="spin" class="col-md-4"></label>
<span class="col-md-8 spinner spinner-inline" [hidden]="!inProgress"></span>

View File

@ -21,6 +21,7 @@ describe('CreateEditEndpointComponent (inline template)', () => {
"name": "target_01",
"username": "admin",
"password": "",
"insecure": false,
"type": 0
};

View File

@ -91,7 +91,8 @@ export class CreateEditEndpointComponent implements AfterViewChecked, OnDestroy
(this.target.endpoint && this.target.endpoint.trim() !== "") ||
(this.target.name && this.target.name.trim() !== "") ||
(this.target.username && this.target.username.trim() !== "") ||
(this.target.password && this.target.password.trim() !== ""));
(this.target.password && this.target.password.trim() !== "")) ||
this.target.insecure;
} else {
//Edit
return !compareValue(this.target, this.initVal);
@ -104,26 +105,29 @@ export class CreateEditEndpointComponent implements AfterViewChecked, OnDestroy
this.targetForm &&
this.targetForm.valid &&
this.editable &&
(this.targetNameHasChanged || this.endpointHasChanged);
(this.targetNameHasChanged || this.endpointHasChanged || this.checkboxHasChanged);
}
public get inProgress(): boolean {
return this.onGoing || this.testOngoing;
}
public get checkboxHasChanged(): boolean {
return (this.target.insecure !== this.initVal.insecure) ? true : false;
}
ngOnDestroy(): void {
if (this.valueChangesSub) {
this.valueChangesSub.unsubscribe();
}
}
initEndpoint(): Endpoint {
return {
endpoint: "",
name: "",
username: "",
password: "",
insecure: false,
type: 0
};
}
@ -275,20 +279,28 @@ export class CreateEditEndpointComponent implements AfterViewChecked, OnDestroy
if (this.onGoing) {
return;//Avoid duplicated submitting
}
if (!(this.targetNameHasChanged || this.endpointHasChanged)) {
if (!(this.targetNameHasChanged || this.endpointHasChanged || this.checkboxHasChanged)) {
return;//Avoid invalid submitting
}
let payload: Endpoint = this.initEndpoint();
if (this.targetNameHasChanged) {
payload.name = this.target.name;
delete payload.endpoint;
}else {
delete payload.name;
}
if (this.endpointHasChanged) {
payload.endpoint = this.target.endpoint;
payload.username = this.target.username;
payload.password = this.target.password;
delete payload.name;
}else {
delete payload.endpoint;
}
if (this.checkboxHasChanged) {
payload.insecure = this.target.insecure;
}else {
delete payload.insecure;
}
if (!this.target.id) { return; }
this.onGoing = true;
@ -317,7 +329,7 @@ export class CreateEditEndpointComponent implements AfterViewChecked, OnDestroy
handleErrorMessageKey(status: number): string {
switch (status) {
case 409: this
case 409:
return 'DESTINATION.CONFLICT_NAME';
case 400:
return 'DESTINATION.INVALID_NAME';
@ -356,7 +368,7 @@ export class CreateEditEndpointComponent implements AfterViewChecked, OnDestroy
keyNumber++;
}
}
if (keyNumber !== 4) {
if (keyNumber !== 5) {
return;
}

View File

@ -110,6 +110,7 @@ describe('CreateEditRuleComponent (inline template)', ()=>{
"name": "target_01",
"username": "admin",
"password": "",
"insecure": false,
"type": 0
},
{
@ -118,6 +119,7 @@ describe('CreateEditRuleComponent (inline template)', ()=>{
"name": "target_02",
"username": "AAA",
"password": "",
"insecure": false,
"type": 0
},
{
@ -126,6 +128,7 @@ describe('CreateEditRuleComponent (inline template)', ()=>{
"name": "target_03",
"username": "admin",
"password": "",
"insecure": false,
"type": 0
},
{
@ -134,6 +137,7 @@ describe('CreateEditRuleComponent (inline template)', ()=>{
"name": "target_04",
"username": "",
"password": "",
"insecure": true,
"type": 0
}
];

View File

@ -123,6 +123,7 @@ export class CreateEditRuleComponent implements AfterViewChecked {
name: '',
username: '',
password: '',
insecure: false,
type: 0
};
}

View File

@ -19,6 +19,7 @@ export const ENDPOINT_TEMPLATE: string = `
<clr-datagrid [clrDgLoading]="loading">
<clr-dg-column [clrDgField]="'name'">{{'DESTINATION.NAME' | translate}}</clr-dg-column>
<clr-dg-column [clrDgField]="'endpoint'">{{'DESTINATION.URL' | translate}}</clr-dg-column>
<clr-dg-column [clrDgField]="'insecure'">{{'CONFIG.VERIFY_REMOTE_CERT' | translate }}</clr-dg-column>
<clr-dg-column [clrDgSortBy]="creationTimeComparator">{{'DESTINATION.CREATION_TIME' | translate}}</clr-dg-column>
<clr-dg-placeholder>{{'DESTINATION.PLACEHOLDER' | translate }}</clr-dg-placeholder>
<clr-dg-row *clrDgItems="let t of targets" [clrDgItem]='t'>
@ -28,6 +29,9 @@ export const ENDPOINT_TEMPLATE: string = `
</clr-dg-action-overflow>
<clr-dg-cell>{{t.name}}</clr-dg-cell>
<clr-dg-cell>{{t.endpoint}}</clr-dg-cell>
<clr-dg-cell>
<clr-checkbox name="insecure" [clrChecked]="t.insecure"> </clr-checkbox>
</clr-dg-cell>
<clr-dg-cell>{{t.creation_time | date: 'short'}}</clr-dg-cell>
</clr-dg-row>
<clr-dg-footer>

View File

@ -25,6 +25,7 @@ describe('EndpointComponent (inline template)', () => {
"name": "target_01",
"username": "admin",
"password": "",
"insecure": true,
"type": 0
},
{
@ -33,6 +34,7 @@ describe('EndpointComponent (inline template)', () => {
"name": "target_02",
"username": "AAA",
"password": "",
"insecure": false,
"type": 0
},
{
@ -41,6 +43,7 @@ describe('EndpointComponent (inline template)', () => {
"name": "target_03",
"username": "admin",
"password": "",
"insecure": false,
"type": 0
},
{
@ -49,6 +52,7 @@ describe('EndpointComponent (inline template)', () => {
"name": "target_04",
"username": "",
"password": "",
"insecure": false,
"type": 0
}
];
@ -59,6 +63,7 @@ describe('EndpointComponent (inline template)', () => {
"name": "target_01",
"username": "admin",
"password": "",
"insecure": false,
"type": 0
};

View File

@ -69,6 +69,7 @@ export class EndpointComponent implements OnInit {
name: "",
username: "",
password: "",
insecure: false,
type: 0
};
}

View File

@ -107,6 +107,7 @@ describe('Replication Component (inline template)', ()=>{
"name": "target_01",
"username": "admin",
"password": "",
"insecure": false,
"type": 0
},
{
@ -115,6 +116,7 @@ describe('Replication Component (inline template)', ()=>{
"name": "target_02",
"username": "AAA",
"password": "",
"insecure": false,
"type": 0
},
{
@ -123,6 +125,7 @@ describe('Replication Component (inline template)', ()=>{
"name": "target_03",
"username": "admin",
"password": "",
"insecure": false,
"type": 0
},
{
@ -131,6 +134,7 @@ describe('Replication Component (inline template)', ()=>{
"name": "target_04",
"username": "",
"password": "",
"insecure": false,
"type": 0
}
];

View File

@ -73,6 +73,7 @@ export interface Endpoint extends Base {
name: string;
username?: string;
password?: string;
insecure: boolean;
type: number;
}

View File

@ -31,7 +31,7 @@
"clarity-icons": "^0.9.8",
"clarity-ui": "^0.9.8",
"core-js": "^2.4.1",
"harbor-ui": "0.4.83",
"harbor-ui": "0.4.85",
"intl": "^1.2.5",
"mutationobserver-shim": "^0.3.2",
"ngx-cookie": "^1.0.0",

View File

@ -6,9 +6,6 @@
<li role="presentation" class="nav-item">
<button id="config-auth" class="btn btn-link nav-link active" aria-controls="authentication" [class.active]='isCurrentTabLink("config-auth")' type="button" (click)='tabLinkClick("config-auth")'>{{'CONFIG.AUTH' | translate }}</button>
</li>
<li role="presentation" class="nav-item">
<button id="config-replication" class="btn btn-link nav-link" aria-controls="replication" [class.active]='isCurrentTabLink("config-replication")' type="button" (click)='tabLinkClick("config-replication")'>{{'CONFIG.REPLICATION' | translate }}</button>
</li>
<li role="presentation" class="nav-item">
<button id="config-email" class="btn btn-link nav-link" aria-controls="email" [class.active]='isCurrentTabLink("config-email")' type="button" (click)='tabLinkClick("config-email")'>{{'CONFIG.EMAIL' | translate }}</button>
</li>
@ -22,9 +19,6 @@
<section id="authentication" role="tabpanel" aria-labelledby="config-auth" [hidden]='!isCurrentTabContent("authentication")'>
<config-auth [ldapConfig]="allConfig"></config-auth>
</section>
<section id="replication" role="tabpanel" aria-labelledby="config-replication" [hidden]='!isCurrentTabContent("replication")'>
<replication-config [(replicationConfig)]="allConfig"></replication-config>
</section>
<section id="email" role="tabpanel" aria-labelledby="config-email" [hidden]='!isCurrentTabContent("email")'>
<config-email [mailConfig]="allConfig"></config-email>
</section>

View File

@ -30,7 +30,6 @@ import {
Configuration,
StringValueItem,
ComplexValueItem,
ReplicationConfigComponent,
SystemSettingsComponent,
VulnerabilityConfigComponent,
ClairDBStatus
@ -59,7 +58,6 @@ export class ConfigurationComponent implements OnInit, OnDestroy {
testingMailOnGoing: boolean = false;
testingLDAPOnGoing: boolean = false;
@ViewChild(ReplicationConfigComponent) replicationConfig: ReplicationConfigComponent;
@ViewChild(SystemSettingsComponent) systemSettingsConfig: SystemSettingsComponent;
@ViewChild(VulnerabilityConfigComponent) vulnerabilityConfig: VulnerabilityConfigComponent;
@ViewChild(ConfigurationEmailComponent) mailConfig: ConfigurationEmailComponent;
@ -170,9 +168,7 @@ export class ConfigurationComponent implements OnInit, OnDestroy {
}
public isValid(): boolean {
return this.replicationConfig &&
this.replicationConfig.isValid &&
this.systemSettingsConfig &&
return this.systemSettingsConfig &&
this.systemSettingsConfig.isValid &&
this.mailConfig &&
this.mailConfig.isValid() &&

View File

@ -102,7 +102,7 @@ Project Creation Should Not Display
Switch To System Settings
Sleep 1
Click Element xpath=//clr-main-container//nav//ul/li[3]
Click Element xpath=//config//ul/li[4]
Click Element xpath=//*[@id="config-system"]
Modify Token Expiration
[Arguments] ${minutes}

View File

@ -176,23 +176,6 @@ Test Case - Edit Self-Registration
Enable Self Reg
Close Browser
Test Case - Edit Verify Remote Cert
Init Chrome Driver
Sign In Harbor ${HARBOR_URL} %{HARBOR_ADMIN} %{HARBOR_PASSWORD}
Switch To System Replication
Check Verify Remote Cert
Logout Harbor
Sign In Harbor ${HARBOR_URL} %{HARBOR_ADMIN} %{HARBOR_PASSWORD}
Switch To System Replication
Should Verify Remote Cert Be Enabled
#restore setting
Check Verify Remote Cert
Close Browser
Test Case - Edit Email Settings
Init Chrome Driver
Sign In Harbor ${HARBOR_URL} %{HARBOR_ADMIN} %{HARBOR_PASSWORD}

View File

@ -54,4 +54,5 @@ Changelog for harbor database schema
- create table `project_metadata`
- insert data into table `project_metadata`
- delete column `public` from table `project`
- delete column `public` from table `project`
- add column `insecure` to table `replication_target`