Merge pull request #3416 from ywk253100/171020_insecure_target

Move certificate verification to target level
This commit is contained in:
Daniel Jiang 2017-10-20 18:16:03 +08:00 committed by GitHub
commit 4d4b27b400
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

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