Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Security Observability: Add image scanning #541

Open
wants to merge 36 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
4ed17a6
Work in progress Analysis tools
nwmac Feb 13, 2020
31996f2
Thursday updates
nwmac Feb 14, 2020
444d206
Friday fixes and improvements
nwmac Feb 14, 2020
cead6c9
Friday code
nwmac Feb 14, 2020
d79a8a8
Merge remote-tracking branch 'origin/master' into hackweek-analysis-t…
nwmac Feb 20, 2020
e3fb8ef
Separate analyzers into a separate container
nwmac Mar 3, 2020
ced1f74
Merge remote-tracking branch 'origin/master' into hackweek-analysis-t…
nwmac Mar 3, 2020
4511eda
Wire in analyzers container in the Helm chart
nwmac Mar 3, 2020
45c59dc
Hide analysis UI features when not enabled
nwmac Mar 3, 2020
dd3de22
Fix sidepanel bug with fallback metadata
nwmac Mar 3, 2020
93485d6
Bug fix for change in way tab links are hidden
nwmac Mar 3, 2020
9285754
Remove debug logging
nwmac Mar 4, 2020
d1e64f9
Add refresh button to report selector drop down. Change no reports icon
nwmac Mar 4, 2020
0ed1e7a
Add support for adding breadcrumbs in the sub nav bar
nwmac Mar 4, 2020
82b655f
Fix unit tests
nwmac Mar 4, 2020
04d7885
Fix format issues
nwmac Mar 4, 2020
82e98ba
Final front-end unit test fixes
nwmac Mar 4, 2020
640f7f2
Fix issues when deploying via Helm with mariadb
nwmac Mar 5, 2020
2d11498
Analyzers container fix. Allow helm chart to be packaged.
nwmac Mar 5, 2020
f6ba4f2
Build script fixes
nwmac Mar 5, 2020
a4cffc2
Remove file
nwmac Apr 20, 2020
5ca9e33
Merge remote-tracking branch 'origin/master' into hackweek-analysis-t…
nwmac Apr 20, 2020
fd0ece7
WIP: Add support for Clair image scanning
nwmac Apr 28, 2020
4f82d57
Use klar
nwmac Apr 28, 2020
59249e0
Remove binary
nwmac Apr 28, 2020
bbec646
Add clair helm chart for dev
nwmac Apr 28, 2020
bc23db6
Merge branch 'scanners-clair' of github.com:SUSE/stratos into scanner…
nwmac Apr 28, 2020
8760617
Fixes
nwmac Apr 28, 2020
fe8244d
USe end var for clair server address
nwmac Apr 28, 2020
adae0e6
Latest updates
nwmac Apr 29, 2020
b6989de
Improvements
nwmac Apr 29, 2020
165f347
Minor fixes
nwmac Apr 29, 2020
0885384
Tweak
nwmac Apr 29, 2020
2b280f6
Fix 1.16 detecton issue with the analyzers
nwmac May 6, 2020
5b96108
Chart fixes
nwmac May 6, 2020
4807142
Merge branch 'scanners-clair' of github.com:SUSE/stratos into scanner…
nwmac May 6, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ src/jetstream/console-database.db
src/jetstream/config.properties
src/jetstream/db/dbconf.yml
src/jetstream/plugins/monocular/chart-repo/chartrepo
src/jetstream/plugins/analysis/container/analyzers

# Customisations

Expand Down
5 changes: 5 additions & 0 deletions custom-src/deploy/kubernetes/custom-build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,9 @@ function custom_image_build() {
# Build and push an image for the Helm Repo Sync Tool
log "-- Building/publishing Monocular Chart Repo Sync Tool"
patchAndPushImage stratos-chartsync Dockerfile "${STRATOS_PATH}/src/jetstream/plugins/monocular/chart-repo"

# Analzyers container
log "-- Building/publishing Stratos Analyzers"
patchAndPushImage stratos-analyzers Dockerfile "${STRATOS_PATH}/src/jetstream/plugins/analysis/container"

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<div *ngIf="analyzers$ | async as analyzers">

<mat-menu #appMenu="matMenu">
<button (click)="onSelected(analyzer)" mat-menu-item *ngFor="let analyzer of analyzers">{{ analyzer.title }}</button>
<mat-divider></mat-divider>
<button (click)="refreshReports($event)" mat-menu-item>
<mat-icon>refresh</mat-icon>
<span>Refresh</span>
</button>
</mat-menu>

<button mat-button [matMenuTriggerFor]="appMenu" *ngIf="analyzers.length > 0">
<span>{{ prompt }}</span>
<span *ngIf="selection">: {{ selection.title }}</span>
<mat-icon>arrow_drop_down</mat-icon>
</button>

</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { MDAppModule } from './../../../../core/md.module';

import { AnalysisReportSelectorComponent } from './analysis-report-selector.component';
import { KubernetesAnalysisService } from '../../services/kubernetes.analysis.service';
import { KubernetesEndpointService } from '../../services/kubernetes-endpoint.service';
import { KubeBaseGuidMock, KubernetesBaseTestModules } from '../../kubernetes.testing.module';

describe('AnalysisReportSelectorComponent', () => {
let component: AnalysisReportSelectorComponent;
let fixture: ComponentFixture<AnalysisReportSelectorComponent>;

beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ AnalysisReportSelectorComponent ],
imports: [
KubernetesBaseTestModules,
MDAppModule
],
providers: [
KubernetesAnalysisService,
KubernetesEndpointService,
KubeBaseGuidMock,
]
})
.compileComponents();
}));

beforeEach(() => {
fixture = TestBed.createComponent(AnalysisReportSelectorComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { Observable, Subject } from 'rxjs';
import { Component, OnInit, Input, EventEmitter, Output } from '@angular/core';
import { KubernetesAnalysisService } from '../../services/kubernetes.analysis.service';
import { map, first } from 'rxjs/operators';
import * as moment from 'moment';

@Component({
selector: 'app-analysis-report-selector',
templateUrl: './analysis-report-selector.component.html',
styleUrls: ['./analysis-report-selector.component.scss']
})
export class AnalysisReportSelectorComponent implements OnInit {

public selection = { title: 'None' };

public analyzers$ = new Subject<any>();

@Input() endpoint;
@Input() path;
@Input() prompt = 'Overlay Analysis';
@Input() allowNone = true;
@Input() autoSelect;

@Output() selected = new EventEmitter<any>();
@Output() reportCount = new EventEmitter<number>();

autoSelected = false;

constructor(public analysisService: KubernetesAnalysisService) { }

ngOnInit() {
this.analyzers$.pipe(first()).subscribe(reports => {
// Auto-select first report
if (!this.autoSelected && this.autoSelect && reports.length > 0) {
this.onSelected(reports[0]);
}
});

this.fetchReports();
}

private fetchReports() {
this.analysisService.getByPath(this.endpoint, this.path).pipe(
map(d => {
const res = [];
if (this.allowNone) {
res.push({title: 'None'});
}
if (d) {
d.forEach(r => {
const c = {... r};
const title = c.type.substr(0, 1).toUpperCase() + c.type.substr(1);
const age = moment(c.created).fromNow(true);
c.title = `${title} (${age})`;
res.push(c);
});
}
this.reportCount.next(res.length);
return res;
})
).subscribe(data => {
this.analyzers$.next(data);
});
}

// Selection changed
public onSelected(d) {
this.selection = d;
if (!d.id) {
this.selected.emit(null);
} else {
this.selected.next(d);
}
}

public refreshReports($event: MouseEvent) {
this.analysisService.refresh();
this.fetchReports();
$event.preventDefault();
$event.cancelBubble = true;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<template #reportViewer></template>
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';

import { AnalysisReportViewerComponent } from './analysis-report-viewer.component';
import { KubernetesBaseTestModules } from '../kubernetes.testing.module';

describe('AnalysisReportViewerComponent', () => {
let component: AnalysisReportViewerComponent;
let fixture: ComponentFixture<AnalysisReportViewerComponent>;

beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ AnalysisReportViewerComponent ],
imports: [
KubernetesBaseTestModules,
]
})
.compileComponents();
}));

beforeEach(() => {
fixture = TestBed.createComponent(AnalysisReportViewerComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import {
Component,
ComponentFactoryResolver,
ComponentRef,
Input,
OnDestroy,
OnInit,
Type,
ViewChild,
ViewContainerRef,
} from '@angular/core';

import { ClairReportViewerComponent } from './clair-report-viewer/clair-report-viewer.component';
import { KubeScoreReportViewerComponent } from './kube-score-report-viewer/kube-score-report-viewer.component';
import { PopeyeReportViewerComponent } from './popeye-report-viewer/popeye-report-viewer.component';

export interface IReportViewer {
// setReport(report);
report: any;
}

@Component({
selector: 'app-analysis-report-viewer',
templateUrl: './analysis-report-viewer.component.html',
styleUrls: ['./analysis-report-viewer.component.scss']
})
export class AnalysisReportViewerComponent implements OnInit, OnDestroy {

// Component reference for the dynamically created auth form
@ViewChild('reportViewer', { read: ViewContainerRef, static: true })
public container: ViewContainerRef;
private reportComponentRef: ComponentRef<IReportViewer>;

private id: string;

@Input('report')
set report(report: any) {
if (report === null || report.id === this.id) {
return;
}
this.id = report.id;
this.updateReport(report);
}

constructor(
private resolver: ComponentFactoryResolver,
) { }

ngOnInit() {
}

updateReport(report) {
switch (report.format) {
case 'popeye':
this.createComponent(PopeyeReportViewerComponent, report);
break;
case 'kubescore':
this.createComponent(KubeScoreReportViewerComponent, report);
break;
case 'clair':
this.createComponent(ClairReportViewerComponent, report);
break;
}
}

// Dynamically create the component for the report type type
createComponent(component: Type<IReportViewer>, report) {
if (!component || !this.container) {
return;
}

if (this.reportComponentRef) {
this.reportComponentRef.destroy();
}
const factory = this.resolver.resolveComponentFactory<IReportViewer>(component);
this.reportComponentRef = this.container.createComponent<IReportViewer>(factory);
// this.reportComponentRef.instance.setReport(report);
this.reportComponentRef.instance.report = report;
}

ngOnDestroy() {
if (this.reportComponentRef) {
this.reportComponentRef.destroy();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<div *ngIf="report" class="clair-detail">
<div class="clair-detail__header">
<div class="clair-detail__header-label"><b>Image: </b>{{ report.name }}</div>
</div>
<div class="clair-detail__main">
<div class="clair-detail__inner">
<div class="clair-detail__info-panel">
<div class="clair-detail__info">
<h1>{{ total }} vulnerabilities detected</h1>
<h2 *ngIf="total > 0">{{ patches }} have available patches</h2>
</div>
<div>
<app-clair-report-severity-summary [totals]="totals"></app-clair-report-severity-summary>
</div>
</div>
<app-table *ngIf="total > 0" [dataSource]="dataSource" [columns]="columns"></app-table>
</div>
</div>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@

.clair-detail {

display: flex;
flex-direction: column;
height: calc(100vh - 104px);

&__header {
align-items: center;
display: flex;
height: 40px;
font-size: 14px;
flex: 0 0 40px;
border-bottom: 1px solid #ccc;
background-color: #eee;
font-weight: bold;
opacity: 0.7;
padding: 0 10px;
}

&__header-label {
flex: 1;
}

&__header-count {
background-color: #ccc;
border-radius: 6px;
flex: 0;
font-size: 12px;
padding: 2px 10px;
}

&__main {
display: flex;
flex: 1;
height: calc(100vh - 144px);
overflow-y: auto;
}

&__inner {
width: 100%;
}

&__info {
padding: 10px;

h1 {
font-size: 18px;
}
h2 {
font-size: 16px;
}
}

&__info-panel {
display: flex;
flex-direction: row;

> div {
flex: 1;
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';

import { ClairReportDetailComponent } from './clair-report-detail.component';

describe('ClairReportDetailComponent', () => {
let component: ClairReportDetailComponent;
let fixture: ComponentFixture<ClairReportDetailComponent>;

beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ ClairReportDetailComponent ]
})
.compileComponents();
}));

beforeEach(() => {
fixture = TestBed.createComponent(ClairReportDetailComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});
});
Loading