Compare commits

..

7 Commits

Author SHA1 Message Date
dwertent
8f9b46cdbe update prometheus format 2022-03-16 09:25:45 +02:00
dwertent
06a2fa05be add ks user to dockerfile 2022-03-15 22:10:27 +02:00
dwertent
d26f90b98e update Prometheus yaml 2022-03-15 18:40:42 +02:00
dwertent
9d957b3c77 update output 2022-03-15 17:04:59 +02:00
dwertent
8ec5615569 junit format 2022-03-15 16:50:39 +02:00
dwertent
6477437872 update readme 2022-03-14 19:14:31 +02:00
dwertent
6099f46dea adding docker build 2022-03-14 18:34:34 +02:00
34 changed files with 668 additions and 104 deletions

View File

@@ -51,7 +51,7 @@ jobs:
ArmoERServer: report.armo.cloud
ArmoWebsite: portal.armo.cloud
CGO_ENABLED: 0
run: python3 --version && python3 build.py
run: cd cmd && python3 --version && python3 build.py
- name: Smoke Testing
env:

View File

@@ -35,7 +35,7 @@ jobs:
ArmoERServer: report.armo.cloud
ArmoWebsite: portal.armo.cloud
CGO_ENABLED: 0
run: python3 --version && python3 build.py
run: cd cmd && python3 --version && python3 build.py
- name: Smoke Testing
env:

View File

@@ -19,8 +19,14 @@ jobs:
with:
go-version: 1.17
- name: Test
run: go test -v ./...
- name: Test cmd pkg
run: cd cmd && go test -v ./...
- name: Test core pkg
run: cd core && go test -v ./...
- name: Test httphandler pkg
run: cd httphandler && go test -v ./...
- name: Build
env:
@@ -30,7 +36,7 @@ jobs:
ArmoERServer: report.armo.cloud
ArmoWebsite: portal.armo.cloud
CGO_ENABLED: 0
run: python3 --version && python3 build.py
run: cd cmd && python3 --version && python3 build.py
- name: Smoke Testing
env:

View File

@@ -18,14 +18,27 @@ RUN pip3 install --no-cache --upgrade pip setuptools
WORKDIR /work
ADD . .
# build kubescape server
WORKDIR /work/httphandler
RUN python build.py
RUN ls -ltr build/ubuntu-latest
FROM alpine
COPY --from=builder /work/build/ubuntu-latest/kubescape /usr/bin/kubescape
# build kubescape cmd
WORKDIR /work/cmd
RUN python build.py
# # Download the frameworks. Use the "--use-default" flag when running kubescape
# RUN kubescape download framework nsa && kubescape download framework mitre
RUN /work/build/ubuntu-latest/kubescape download artifacts -o /work/artifacts
FROM alpine
RUN addgroup -S ks && adduser -S ks -G ks
USER ks
WORKDIR /home/ks/
COPY --from=builder /work/httphandler/build/ubuntu-latest/kubescape /usr/bin/kubescape
COPY --from=builder /work/build/ubuntu-latest/kubescape /usr/bin/kscli
RUN mkdir /home/ks/.kubescape && chmod 777 -R /home/ks/.kubescape
COPY --from=builder /work/artifacts/ /home/ks/.kubescape
ENTRYPOINT ["kubescape"]

View File

@@ -18,7 +18,7 @@ def checkStatus(status, msg):
def getBuildDir():
currentPlatform = platform.system()
buildDir = "build/"
buildDir = "../build/"
if currentPlatform == "Windows": buildDir += "windows-latest"
elif currentPlatform == "Linux": buildDir += "ubuntu-latest"
@@ -70,7 +70,7 @@ def main():
ldflags += " -X {}={}".format(WEBSITE_CONST, ArmoWebsite)
if ArmoAuthServer:
ldflags += " -X {}={}".format(AUTH_SERVER_CONST, ArmoAuthServer)
build_command = ["go", "build", "-o", ks_file, "-ldflags" ,ldflags]
print("Building kubescape and saving here: {}".format(ks_file))

View File

@@ -7,7 +7,7 @@ replace github.com/armosec/kubescape/core => ../core
require (
github.com/armosec/k8s-interface v0.0.68
github.com/armosec/kubescape/core v0.0.0-00010101000000-000000000000
github.com/armosec/opa-utils v0.0.116
github.com/armosec/opa-utils v0.0.118
github.com/armosec/rbac-utils v0.0.14
github.com/google/uuid v1.3.0
github.com/mattn/go-isatty v0.0.14

View File

@@ -109,8 +109,8 @@ github.com/armosec/k8s-interface v0.0.66/go.mod h1:vwprS8qn/iowd5yf0JHpqDsLA5I8W
github.com/armosec/k8s-interface v0.0.68 h1:6CtSakISiI47YHkxh+Va9FzZQIBkWa6g9sbiNxq1Zkk=
github.com/armosec/k8s-interface v0.0.68/go.mod h1:PeWn41C2uenZi+xfZdyFF/zG5wXACA00htQyknDUWDE=
github.com/armosec/opa-utils v0.0.64/go.mod h1:6tQP8UDq2EvEfSqh8vrUdr/9QVSCG4sJfju1SXQOn4c=
github.com/armosec/opa-utils v0.0.116 h1:3oWuhcpI+MJD/CktEStU1BA0feGNwsCbQrI3ifVfzMs=
github.com/armosec/opa-utils v0.0.116/go.mod h1:gap+EaLG5rnyqvIRGxtdNDC9y7VvoGNm90zK8Ls7avQ=
github.com/armosec/opa-utils v0.0.118 h1:ZX1crwVQmo+sDv+jmTNLbDYfApUBzlgPhD8QI2GCJX0=
github.com/armosec/opa-utils v0.0.118/go.mod h1:gap+EaLG5rnyqvIRGxtdNDC9y7VvoGNm90zK8Ls7avQ=
github.com/armosec/rbac-utils v0.0.1/go.mod h1:pQ8CBiij8kSKV7aeZm9FMvtZN28VgA7LZcYyTWimq40=
github.com/armosec/rbac-utils v0.0.14 h1:CKYKcgqJEXWF2Hen/B1pVGtS3nDAG1wp9dDv6oNtq90=
github.com/armosec/rbac-utils v0.0.14/go.mod h1:Ex/IdGWhGv9HZq6Hs8N/ApzCKSIvpNe/ETqDfnuyah0=

View File

@@ -133,15 +133,6 @@ func (scanInfo *ScanInfo) setUseArtifactsFrom() {
scanInfo.UseExceptions = filepath.Join(scanInfo.UseArtifactsFrom, localExceptionsFilename)
}
func (scanInfo *ScanInfo) setUseExceptions() {
if scanInfo.UseExceptions != "" {
// load exceptions from file
scanInfo.ExceptionsGetter = getter.NewLoadPolicy([]string{scanInfo.UseExceptions})
} else {
scanInfo.ExceptionsGetter = getter.GetArmoAPIConnector()
}
}
func (scanInfo *ScanInfo) setUseFrom() {
if scanInfo.UseDefault {
for _, policy := range scanInfo.PolicyIdentifier {

View File

@@ -13,7 +13,8 @@ import (
"golang.org/x/mod/semver"
)
const SKIP_VERSION_CHECK = "KUBESCAPE_SKIP_UPDATE_CHECK"
const SKIP_VERSION_CHECK_DEPRECATED = "KUBESCAPE_SKIP_UPDATE_CHECK"
const SKIP_VERSION_CHECK = "KS_SKIP_UPDATE_CHECK"
var BuildNumber string
@@ -29,6 +30,8 @@ func NewIVersionCheckHandler() IVersionCheckHandler {
}
if v, ok := os.LookupEnv(SKIP_VERSION_CHECK); ok && pkgutils.StringToBool(v) {
return NewVersionCheckHandlerMock()
} else if v, ok := os.LookupEnv(SKIP_VERSION_CHECK_DEPRECATED); ok && pkgutils.StringToBool(v) {
return NewVersionCheckHandlerMock()
}
return NewVersionCheckHandler()
}

View File

@@ -2,6 +2,7 @@ package core
import (
"fmt"
"os"
"path/filepath"
"strings"
@@ -30,6 +31,9 @@ func DownloadSupportCommands() []string {
func (ks *Kubescape) Download(downloadInfo *metav1.DownloadInfo) error {
setPathandFilename(downloadInfo)
if err := os.MkdirAll(downloadInfo.Path, os.ModePerm); err != nil {
return err
}
if err := downloadArtifact(downloadInfo, downloadFunc); err != nil {
return err
}
@@ -86,6 +90,9 @@ func downloadConfigInputs(downloadInfo *metav1.DownloadInfo) error {
if downloadInfo.FileName == "" {
downloadInfo.FileName = fmt.Sprintf("%s.json", downloadInfo.Target)
}
if controlInputs == nil {
return fmt.Errorf("failed to download controlInputs - received an empty objects")
}
// save in file
err = getter.SaveInFile(controlInputs, filepath.Join(downloadInfo.Path, downloadInfo.FileName))
if err != nil {
@@ -148,6 +155,9 @@ func downloadFramework(downloadInfo *metav1.DownloadInfo) error {
if err != nil {
return err
}
if framework == nil {
return fmt.Errorf("failed to download framework - received an empty objects")
}
downloadTo := filepath.Join(downloadInfo.Path, downloadInfo.FileName)
err = getter.SaveInFile(framework, downloadTo)
if err != nil {
@@ -175,6 +185,9 @@ func downloadControl(downloadInfo *metav1.DownloadInfo) error {
if err != nil {
return err
}
if controls == nil {
return fmt.Errorf("failed to download control - received an empty objects")
}
downloadTo := filepath.Join(downloadInfo.Path, downloadInfo.FileName)
err = getter.SaveInFile(controls, downloadTo)
if err != nil {

View File

@@ -5,7 +5,7 @@ go 1.17
require (
github.com/armosec/armoapi-go v0.0.58
github.com/armosec/k8s-interface v0.0.68
github.com/armosec/opa-utils v0.0.116
github.com/armosec/opa-utils v0.0.118
github.com/armosec/rbac-utils v0.0.14
github.com/armosec/utils-go v0.0.3
github.com/armosec/utils-k8s-go v0.0.3

View File

@@ -109,8 +109,8 @@ github.com/armosec/k8s-interface v0.0.66/go.mod h1:vwprS8qn/iowd5yf0JHpqDsLA5I8W
github.com/armosec/k8s-interface v0.0.68 h1:6CtSakISiI47YHkxh+Va9FzZQIBkWa6g9sbiNxq1Zkk=
github.com/armosec/k8s-interface v0.0.68/go.mod h1:PeWn41C2uenZi+xfZdyFF/zG5wXACA00htQyknDUWDE=
github.com/armosec/opa-utils v0.0.64/go.mod h1:6tQP8UDq2EvEfSqh8vrUdr/9QVSCG4sJfju1SXQOn4c=
github.com/armosec/opa-utils v0.0.116 h1:3oWuhcpI+MJD/CktEStU1BA0feGNwsCbQrI3ifVfzMs=
github.com/armosec/opa-utils v0.0.116/go.mod h1:gap+EaLG5rnyqvIRGxtdNDC9y7VvoGNm90zK8Ls7avQ=
github.com/armosec/opa-utils v0.0.118 h1:ZX1crwVQmo+sDv+jmTNLbDYfApUBzlgPhD8QI2GCJX0=
github.com/armosec/opa-utils v0.0.118/go.mod h1:gap+EaLG5rnyqvIRGxtdNDC9y7VvoGNm90zK8Ls7avQ=
github.com/armosec/rbac-utils v0.0.1/go.mod h1:pQ8CBiij8kSKV7aeZm9FMvtZN28VgA7LZcYyTWimq40=
github.com/armosec/rbac-utils v0.0.14 h1:CKYKcgqJEXWF2Hen/B1pVGtS3nDAG1wp9dDv6oNtq90=
github.com/armosec/rbac-utils v0.0.14/go.mod h1:Ex/IdGWhGv9HZq6Hs8N/ApzCKSIvpNe/ETqDfnuyah0=

View File

@@ -112,10 +112,10 @@ func TestProcessResourcesResult(t *testing.T) {
assert.Equal(t, 0, len(summaryDetails.ListResourcesIDs().Passed()))
// test control listing
assert.Equal(t, len(res.ListControlsIDs(nil).All()), len(summaryDetails.ListControls().All()))
assert.Equal(t, len(res.ListControlsIDs(nil).Passed()), len(summaryDetails.ListControls().Passed()))
assert.Equal(t, len(res.ListControlsIDs(nil).Failed()), len(summaryDetails.ListControls().Failed()))
assert.Equal(t, len(res.ListControlsIDs(nil).Excluded()), len(summaryDetails.ListControls().Excluded()))
assert.Equal(t, len(res.ListControlsIDs(nil).All()), summaryDetails.NumberOfControls().All())
assert.Equal(t, len(res.ListControlsIDs(nil).Passed()), summaryDetails.NumberOfControls().Passed())
assert.Equal(t, len(res.ListControlsIDs(nil).Failed()), summaryDetails.NumberOfControls().Failed())
assert.Equal(t, len(res.ListControlsIDs(nil).Excluded()), summaryDetails.NumberOfControls().Excluded())
assert.True(t, summaryDetails.GetStatus().IsFailed())
opaSessionObj.Exceptions = []armotypes.PostureExceptionPolicy{*mocks.MockExceptionAllKinds(&armotypes.PosturePolicy{FrameworkName: frameworks[0].Name})}

View File

@@ -128,7 +128,7 @@ func listTestsSuite(results *cautils.OPASessionObj) []JUnitTestSuite {
var testSuites []JUnitTestSuite
// control scan
if len(results.Report.SummaryDetails.ListFrameworks().All()) == 0 {
if len(results.Report.SummaryDetails.ListFrameworks()) == 0 {
testSuite := JUnitTestSuite{}
testSuite.Failures = results.Report.SummaryDetails.NumberOfControls().Failed()
testSuite.Timestamp = results.Report.ReportGenerationTime.String()
@@ -147,7 +147,7 @@ func listTestsSuite(results *cautils.OPASessionObj) []JUnitTestSuite {
testSuite.ID = i
testSuite.Name = f.Name
testSuite.Properties = properties(f.Score)
testSuite.TestCases = testsCases(results, f.ListControls(), f.GetName())
testSuite.TestCases = testsCases(results, f.GetControls(), f.GetName())
testSuites = append(testSuites, testSuite)
}
@@ -176,7 +176,7 @@ func testsCases(results *cautils.OPASessionObj, controls reportsummary.IControls
testCaseFailure := JUnitFailure{}
testCaseFailure.Type = "Control"
// testCaseFailure.Contents =
testCaseFailure.Message = fmt.Sprintf("Remediation: %s\nMore details: %s\n\n%s", control.GetRemediation(), getControlURL(control.GetID()), strings.Join(resourcesStr, "\n"))
testCaseFailure.Message = fmt.Sprintf("Remediation: %s\nMore details: %s\n\n%s", control.GetRemediation(), getControlLink(control.GetID()), strings.Join(resourcesStr, "\n"))
testCase.Failure = &testCaseFailure
} else if control.GetStatus().IsSkipped() {

View File

@@ -60,7 +60,7 @@ func (pdfPrinter *PdfPrinter) ActionPrint(opaSessionObj *cautils.OPASessionObj)
m := pdf.NewMaroto(consts.Portrait, consts.A4)
pdfPrinter.printHeader(m)
pdfPrinter.printFramework(m, opaSessionObj.Report.SummaryDetails.ListFrameworks().All())
pdfPrinter.printFramework(m, opaSessionObj.Report.SummaryDetails.ListFrameworks())
pdfPrinter.printTable(m, &opaSessionObj.Report.SummaryDetails)
pdfPrinter.printFinalResult(m, &opaSessionObj.Report.SummaryDetails)
@@ -115,7 +115,7 @@ func (pdfPrinter *PdfPrinter) printHeader(m pdf.Maroto) {
}
// Print pdf frameworks after pdf header.
func (pdfPrinter *PdfPrinter) printFramework(m pdf.Maroto, frameworks []reportsummary.IPolicies) {
func (pdfPrinter *PdfPrinter) printFramework(m pdf.Maroto, frameworks []reportsummary.IFrameworkSummary) {
m.Row(10, func() {
m.Text(frameworksScoresToString(frameworks), props.Text{
Align: consts.Center,

View File

@@ -75,7 +75,7 @@ func (prettyPrinter *PrettyPrinter) printSummary(controlName string, controlSumm
}
func (prettyPrinter *PrettyPrinter) printTitle(controlSummary reportsummary.IControlSummary) {
cautils.InfoDisplay(prettyPrinter.writer, "[control: %s - %s] ", controlSummary.GetName(), getControlURL(controlSummary.GetID()))
cautils.InfoDisplay(prettyPrinter.writer, "[control: %s - %s] ", controlSummary.GetName(), getControlLink(controlSummary.GetID()))
switch controlSummary.GetStatus().Status() {
case apis.StatusSkipped:
cautils.InfoDisplay(prettyPrinter.writer, "skipped %v\n", emoji.ConfusedFace)
@@ -188,10 +188,10 @@ func (prettyPrinter *PrettyPrinter) printSummaryTable(summaryDetails *reportsumm
summaryTable.Render()
// For control scan framework will be nil
cautils.InfoTextDisplay(prettyPrinter.writer, frameworksScoresToString(summaryDetails.ListFrameworks().All()))
cautils.InfoTextDisplay(prettyPrinter.writer, frameworksScoresToString(summaryDetails.ListFrameworks()))
}
func frameworksScoresToString(frameworks []reportsummary.IPolicies) string {
func frameworksScoresToString(frameworks []reportsummary.IFrameworkSummary) string {
if len(frameworks) == 1 {
if frameworks[0].GetName() != "" {
return fmt.Sprintf("FRAMEWORK %s\n", frameworks[0].GetName())
@@ -217,6 +217,6 @@ func frameworksScoresToString(frameworks []reportsummary.IPolicies) string {
// sort.Strings(controlNames)
// return controlNames
// }
func getControlURL(controlID string) string {
func getControlLink(controlID string) string {
return fmt.Sprintf("https://hub.armo.cloud/docs/%s", strings.ToLower(controlID))
}

View File

@@ -0,0 +1,55 @@
package v2
import (
"fmt"
"os"
"github.com/armosec/k8s-interface/workloadinterface"
"github.com/armosec/kubescape/core/cautils"
"github.com/armosec/kubescape/core/cautils/logger"
"github.com/armosec/kubescape/core/cautils/logger/helpers"
"github.com/armosec/kubescape/core/pkg/resultshandling/printer"
"github.com/armosec/opa-utils/reporthandling/results/v1/reportsummary"
"github.com/armosec/opa-utils/reporthandling/results/v1/resourcesresults"
)
type PrometheusPrinter struct {
writer *os.File
verboseMode bool
}
func NewPrometheusPrinter(verboseMode bool) *PrometheusPrinter {
return &PrometheusPrinter{
verboseMode: verboseMode,
}
}
func (prometheusPrinter *PrometheusPrinter) SetWriter(outputFile string) {
prometheusPrinter.writer = printer.GetWriter(outputFile)
}
func (prometheusPrinter *PrometheusPrinter) Score(score float32) {
fmt.Printf("\n# Overall risk-score (0- Excellent, 100- All failed)\nkubescape_score %d\n", int(score))
}
func (printer *PrometheusPrinter) generatePrometheusFormat(
resources map[string]workloadinterface.IMetadata,
results map[string]resourcesresults.Result,
summaryDetails *reportsummary.SummaryDetails) *Metrics {
m := &Metrics{}
m.setRiskScores(summaryDetails)
m.setResourcesCounters(resources, results)
return m
}
func (printer *PrometheusPrinter) ActionPrint(opaSessionObj *cautils.OPASessionObj) {
metrics := printer.generatePrometheusFormat(opaSessionObj.AllResources, opaSessionObj.ResourcesResult, &opaSessionObj.Report.SummaryDetails)
logOUtputFile(printer.writer.Name())
if _, err := printer.writer.Write([]byte(metrics.String())); err != nil {
logger.L().Error("failed to write results", helpers.Error(err))
}
}

View File

@@ -0,0 +1,252 @@
package v2
import (
"fmt"
"github.com/armosec/k8s-interface/workloadinterface"
"github.com/armosec/opa-utils/reporthandling/apis"
"github.com/armosec/opa-utils/reporthandling/results/v1/reportsummary"
"github.com/armosec/opa-utils/reporthandling/results/v1/resourcesresults"
)
type metricsName string
const (
metricsFrameworkScore metricsName = "kubescape_risk_score_framework"
metricsControlScore metricsName = "kubescape_risk_score_control"
metricsScore metricsName = "kubescape_risk_score"
metricsresourceFailed metricsName = "kubescape_resource_controls_number_of_failed"
metricsresourcePassed metricsName = "kubescape_resource_controls_number_of_passed"
metricsresourceExcluded metricsName = "kubescape_resource_controls_number_of_exclude"
)
func (mrs *mRiskScore) string() string {
r := fmt.Sprintf("resourcesCountFailed=\"%d\"", mrs.resourcesCountFailed) + ","
r += fmt.Sprintf("resourcesCountExcluded=\"%d\"", mrs.resourcesCountExcluded) + ","
r += fmt.Sprintf("resourcesCountPassed=\"%d\"", mrs.resourcesCountPassed) + ","
r += fmt.Sprintf("controlsCountFailed=\"%d\"", mrs.controlsCountFailed) + ","
r += fmt.Sprintf("controlsCountExcluded=\"%d\"", mrs.controlsCountExcluded) + ","
r += fmt.Sprintf("controlsCountPassed=\"%d\"", mrs.controlsCountPassed) + ","
r += fmt.Sprintf("controlsCountSkipped=\"%d\"", mrs.controlsCountSkipped)
return r
}
func (mrs *mRiskScore) value() int {
return mrs.riskScore
}
func (mcrs *mControlRiskScore) string() string {
r := fmt.Sprintf("controlName=\"%s\"", mcrs.controlName) + ","
r += fmt.Sprintf("controlID=\"%s\"", mcrs.controlID) + ","
r += fmt.Sprintf("severity=\"%s\"", mcrs.severity) + ","
r += fmt.Sprintf("resourcesCountFailed=\"%d\"", mcrs.resourcesCountFailed) + ","
r += fmt.Sprintf("resourcesCountExcluded=\"%d\"", mcrs.resourcesCountExcluded) + ","
r += fmt.Sprintf("resourcesCountPassed=\"%d\"", mcrs.resourcesCountPassed) + ","
r += fmt.Sprintf("link=\"%s\"", mcrs.link) + ","
r += fmt.Sprintf("remediation=\"%s\"", mcrs.remediation)
return r
}
func (mcrs *mControlRiskScore) value() int {
return mcrs.riskScore
}
func (mfrs *mFrameworkRiskScore) string() string {
r := fmt.Sprintf("frameworkName=\"%s\"", mfrs.frameworkName) + ","
r += fmt.Sprintf("resourcesCountFailed=\"%d\"", mfrs.resourcesCountFailed) + ","
r += fmt.Sprintf("resourcesCountExcluded=\"%d\"", mfrs.resourcesCountExcluded) + ","
r += fmt.Sprintf("resourcesCountPassed=\"%d\"", mfrs.resourcesCountPassed) + ","
r += fmt.Sprintf("controlsCountFailed=\"%d\"", mfrs.controlsCountFailed) + ","
r += fmt.Sprintf("controlsCountExcluded=\"%d\"", mfrs.controlsCountExcluded) + ","
r += fmt.Sprintf("controlsCountPassed=\"%d\"", mfrs.controlsCountPassed) + ","
r += fmt.Sprintf("controlsCountSkipped=\"%d\"", mfrs.controlsCountSkipped)
return r
}
func (mfrs *mFrameworkRiskScore) value() int {
return mfrs.riskScore
}
func (mrc *mResourceControls) string() string {
r := fmt.Sprintf("apiVersion=\"%s\"", mrc.apiVersion) + ","
r += fmt.Sprintf("kind=\"%s\"", mrc.kind) + ","
r += fmt.Sprintf("namespace=\"%s\"", mrc.namespace) + ","
r += fmt.Sprintf("name=\"%s\"", mrc.name)
return r
}
func (mrc *mResourceControls) value() int {
return mrc.controls
}
func toRowInMetrics(name metricsName, row string, value int) string {
return fmt.Sprintf("%s{%s} %d\n", name, row, value)
}
func (m *Metrics) String() string {
r := toRowInMetrics(metricsScore, m.rs.string(), m.rs.value())
for i := range m.listFrameworks {
r += toRowInMetrics(metricsFrameworkScore, m.listFrameworks[i].string(), m.listFrameworks[i].value())
}
for i := range m.listControls {
r += toRowInMetrics(metricsControlScore, m.listControls[i].string(), m.listControls[i].value())
}
for i := range m.listResourcesControlsFiled {
r += toRowInMetrics(metricsresourceFailed, m.listResourcesControlsFiled[i].string(), m.listResourcesControlsFiled[i].value())
}
for i := range m.listResourcesControlsExcluded {
r += toRowInMetrics(metricsresourceExcluded, m.listResourcesControlsExcluded[i].string(), m.listResourcesControlsExcluded[i].value())
}
for i := range m.listResourcesControlsPassed {
r += toRowInMetrics(metricsresourcePassed, m.listResourcesControlsPassed[i].string(), m.listResourcesControlsPassed[i].value())
}
return r
}
type mRiskScore struct {
resourcesCountPassed int
resourcesCountFailed int
resourcesCountExcluded int
controlsCountPassed int
controlsCountFailed int
controlsCountExcluded int
controlsCountSkipped int
riskScore int // metric
}
type mControlRiskScore struct {
controlName string
controlID string
link string
severity string
remediation string
resourcesCountPassed int
resourcesCountFailed int
resourcesCountExcluded int
riskScore int // metric
}
type mFrameworkRiskScore struct {
frameworkName string
resourcesCountPassed int
resourcesCountFailed int
resourcesCountExcluded int
controlsCountPassed int
controlsCountFailed int
controlsCountExcluded int
controlsCountSkipped int
riskScore int // metric
}
type mResourceControls struct {
name string
namespace string
apiVersion string
kind string
controls int // metric
}
type Metrics struct {
rs mRiskScore
listFrameworks []mFrameworkRiskScore
listControls []mControlRiskScore
listResourcesControlsFiled []mResourceControls
listResourcesControlsPassed []mResourceControls
listResourcesControlsExcluded []mResourceControls
}
func (mrs *mRiskScore) set(resources reportsummary.ICounters, controls reportsummary.ICounters) {
mrs.resourcesCountExcluded = resources.Excluded()
mrs.resourcesCountFailed = resources.Failed()
mrs.resourcesCountPassed = resources.Passed()
mrs.controlsCountExcluded = controls.Excluded()
mrs.controlsCountFailed = controls.Failed()
mrs.controlsCountPassed = controls.Passed()
mrs.controlsCountSkipped = controls.Skipped()
}
func (mfrs *mFrameworkRiskScore) set(resources reportsummary.ICounters, controls reportsummary.ICounters) {
mfrs.resourcesCountExcluded = resources.Excluded()
mfrs.resourcesCountFailed = resources.Failed()
mfrs.resourcesCountPassed = resources.Passed()
mfrs.controlsCountExcluded = controls.Excluded()
mfrs.controlsCountFailed = controls.Failed()
mfrs.controlsCountPassed = controls.Passed()
mfrs.controlsCountSkipped = controls.Skipped()
}
func (mcrs *mControlRiskScore) set(resources reportsummary.ICounters) {
mcrs.resourcesCountExcluded = resources.Excluded()
mcrs.resourcesCountFailed = resources.Failed()
mcrs.resourcesCountPassed = resources.Passed()
}
func (m *Metrics) setRiskScores(summaryDetails *reportsummary.SummaryDetails) {
m.rs.set(summaryDetails.NumberOfResources(), summaryDetails.NumberOfControls())
m.rs.riskScore = int(summaryDetails.GetScore())
for _, fw := range summaryDetails.ListFrameworks() {
mfrs := mFrameworkRiskScore{
frameworkName: fw.GetName(),
riskScore: int(fw.GetScore()),
}
mfrs.set(fw.NumberOfResources(), fw.NumberOfControls())
m.listFrameworks = append(m.listFrameworks, mfrs)
}
for _, control := range summaryDetails.ListControls() {
mcrs := mControlRiskScore{
controlName: control.GetName(),
controlID: control.GetID(),
riskScore: int(control.GetScore()),
link: getControlLink(control.GetID()),
severity: apis.ControlSeverityToString(control.GetScoreFactor()),
remediation: control.GetRemediation(),
}
mcrs.set(control.NumberOfResources())
m.listControls = append(m.listControls, mcrs)
}
}
// return -> (passed, exceluded, failed)
func resourceControlStatusCounters(result *resourcesresults.Result) (int, int, int) {
failed := 0
excluded := 0
passed := 0
for i := range result.ListControls() {
switch result.ListControls()[i].GetStatus(nil).Status() {
case apis.StatusExcluded:
excluded++
case apis.StatusFailed:
failed++
case apis.StatusPassed:
passed++
}
}
return passed, excluded, failed
}
func (m *Metrics) setResourcesCounters(
resources map[string]workloadinterface.IMetadata,
results map[string]resourcesresults.Result) {
for resourceID, result := range results {
r, ok := resources[resourceID]
if !ok {
continue
}
passed, excluded, failed := resourceControlStatusCounters(&result)
mrc := mResourceControls{}
mrc.apiVersion = r.GetApiVersion()
mrc.namespace = r.GetNamespace()
mrc.kind = r.GetKind()
mrc.name = r.GetName()
// append
if passed > 0 {
mrc.controls = passed
m.listResourcesControlsPassed = append(m.listResourcesControlsPassed, mrc)
}
if failed > 0 {
mrc.controls = failed
m.listResourcesControlsFiled = append(m.listResourcesControlsFiled, mrc)
}
if excluded > 0 {
mrc.controls = excluded
m.listResourcesControlsExcluded = append(m.listResourcesControlsExcluded, mrc)
}
}
}

View File

@@ -84,7 +84,7 @@ func NewPrinter(printFormat, formatVersion string, verboseMode bool) printer.IPr
case printer.JunitResultFormat:
return printerv2.NewJunitPrinter(verboseMode)
case printer.PrometheusFormat:
return printerv1.NewPrometheusPrinter(verboseMode)
return printerv2.NewPrometheusPrinter(verboseMode)
case printer.PdfFormat:
return printerv2.NewPdfPrinter()
default:

View File

@@ -5,14 +5,14 @@
Running `kubescape` will start up a webserver on port `8080` which will serve the following paths:
* POST `/v1/scan` - Trigger a kubescape scan. The server will return an ID and will execute the scanning asynchronously
* * `synchronously`: scan synchronously (return results and not ID). Use only in small clusters are with an increased timeout
* * `wait`: scan synchronously (return results and not ID). Use only in small clusters are with an increased timeout
* GET `/v1/results` - Request kubescape scan results
* * query `id=<string>` -> ID returned when triggering the scan action. If empty will return latest results
* * query `id=<string>` -> ID returned when triggering the scan action. ~If empty will return latest results~ (not supported)
* * query `remove` -> Remove results from storage after reading the results
* DELETE `/v1/results` - Delete kubescape scan results from storage If empty will delete latest results
* DELETE `/v1/results` - Delete kubescape scan results from storage. ~If empty will delete latest results~ (not supported)
* * query `id=<string>`: Delete ID of specific results
* * query `all`: Delete all cached results
* GET/POST `/v1/metrics` - will trigger cluster scan. will respond with prometheus metrics once they have been scanned. This will respond 503 if the scan failed.
* GET/POST `/metrics` - will trigger cluster scan. will respond with prometheus metrics once they have been scanned. This will respond 503 if the scan failed.
* `/livez` - will respond 200 is server is alive
* `/readyz` - will respond 200 if server can receive requests
@@ -20,15 +20,16 @@ Running `kubescape` will start up a webserver on port `8080` which will serve th
POST /v1/results
body:
```json
```
{
"format": "", // results format [default: json] (same as 'kubescape scan --format')
"excludedNamespaces": null, // list of namespaces to exclude (same as 'kubescape scan --excluded-namespaces')
"includeNamespaces": null, // list of namespaces to include (same as 'kubescape scan --include-namespaces')
"submit": false, // submit results to Kubescape cloud (same as 'kubescape scan --submit')
"hostScanner": false, // deploy kubescape K8s host-scanner DaemonSet in the scanned cluster (same as 'kubescape scan --enable-host-scan')
"keepLocal": false, // do not submit results to Kubescape cloud (same as 'kubescape scan --keep-local')
"account": "" // account ID (same as 'kubescape scan --account')
"format": <str>, // results format [default: json] (same as 'kubescape scan --format')
"excludedNamespaces": <[]str>, // list of namespaces to exclude (same as 'kubescape scan --excluded-namespaces')
"includeNamespaces": <[]str>, // list of namespaces to include (same as 'kubescape scan --include-namespaces')
"useCachedArtifacts"`: <bool>, // use the cached artifacts instead of downloading (offline support)
"submit": <bool>, // submit results to Kubescape cloud (same as 'kubescape scan --submit')
"hostScanner": <bool>, // deploy kubescape K8s host-scanner DaemonSet in the scanned cluster (same as 'kubescape scan --enable-host-scan')
"keepLocal": <bool>, // do not submit results to Kubescape cloud (same as 'kubescape scan --keep-local')
"account": <str> // account ID (same as 'kubescape scan --account')
}
```
@@ -37,11 +38,10 @@ e.g.:
```bash
curl --header "Content-Type: application/json" \
--request POST \
--data '{"account":"XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX","hostScanner":true, "submit":true}' \
--data '{"hostScanner":true, "submit":true}' \
http://127.0.0.1:8080/v1/scan
```
## Installation into kubernetes
## Examples
The [yaml](ks-prometheus-support.yaml) file will deploy one instance of kubescape (with all relevant dependencies) to run on your cluster
**NOTE** Make sure the configurations suit your cluster (e.g. `serviceType`, namespace, etc.)
* [Prometheus](examples/prometheus/README.md)
* [Microservice](examples/microservice/README.md)

92
httphandler/build.py Normal file
View File

@@ -0,0 +1,92 @@
import os
import sys
import hashlib
import platform
import subprocess
BASE_GETTER_CONST = "github.com/armosec/kubescape/core/cautils/getter"
BE_SERVER_CONST = BASE_GETTER_CONST + ".ArmoBEURL"
ER_SERVER_CONST = BASE_GETTER_CONST + ".ArmoERURL"
WEBSITE_CONST = BASE_GETTER_CONST + ".ArmoFEURL"
AUTH_SERVER_CONST = BASE_GETTER_CONST + ".armoAUTHURL"
def checkStatus(status, msg):
if status != 0:
sys.stderr.write(msg)
exit(status)
def getBuildDir():
currentPlatform = platform.system()
buildDir = "build/"
if currentPlatform == "Windows": return os.path.join(buildDir, "windows-latest")
if currentPlatform == "Linux": return os.path.join(buildDir, "ubuntu-latest")
if currentPlatform == "Darwin": return os.path.join(buildDir, "macos-latest")
raise OSError("Platform %s is not supported!" % (currentPlatform))
def getPackageName():
packageName = "kubescape"
# if platform.system() == "Windows": packageName += ".exe"
return packageName
def main():
print("Building Kubescape")
# print environment variables
# print(os.environ)
# Set some variables
packageName = getPackageName()
buildUrl = "github.com/armosec/kubescape/core/cautils.BuildNumber"
releaseVersion = os.getenv("RELEASE")
ArmoBEServer = os.getenv("ArmoBEServer")
ArmoERServer = os.getenv("ArmoERServer")
ArmoWebsite = os.getenv("ArmoWebsite")
ArmoAuthServer = os.getenv("ArmoAuthServer")
# Create build directory
buildDir = getBuildDir()
ks_file = os.path.join(buildDir, packageName)
hash_file = ks_file + ".sha256"
if not os.path.isdir(buildDir):
os.makedirs(buildDir)
# Build kubescape
ldflags = "-w -s"
if releaseVersion:
ldflags += " -X {}={}".format(buildUrl, releaseVersion)
if ArmoBEServer:
ldflags += " -X {}={}".format(BE_SERVER_CONST, ArmoBEServer)
if ArmoERServer:
ldflags += " -X {}={}".format(ER_SERVER_CONST, ArmoERServer)
if ArmoWebsite:
ldflags += " -X {}={}".format(WEBSITE_CONST, ArmoWebsite)
if ArmoAuthServer:
ldflags += " -X {}={}".format(AUTH_SERVER_CONST, ArmoAuthServer)
build_command = ["go", "build", "-o", ks_file, "-ldflags" ,ldflags]
print("Building kubescape and saving here: {}".format(ks_file))
print("Build command: {}".format(" ".join(build_command)))
status = subprocess.call(build_command)
checkStatus(status, "Failed to build kubescape")
sha256 = hashlib.sha256()
with open(ks_file, "rb") as kube:
sha256.update(kube.read())
with open(hash_file, "w") as kube_sha:
hash = sha256.hexdigest()
print("kubescape hash: {}, file: {}".format(hash, hash_file))
kube_sha.write(sha256.hexdigest())
print("Build Done")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,20 @@
# Kubescape as a microservice
1. Deploy kubescape microservice
```bash
kubectl apply -f https://raw.githubusercontent.com/armosec/kubescape/master/httphandler/examples/prometheus/kubescape.yaml
```
> **NOTE** Make sure the configurations suit your cluster (e.g. `serviceType`, namespace, etc.)
2. Trigger scan
```bash
curl --header "Content-Type: application/json" \
--request POST \
--data '{"account":"XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX","hostScanner":true, "submit":true}' \
http://127.0.0.1:8080/v1/scan
```
3. Get results
```bash
curl --request GET http://127.0.0.1:8080/v1/results -o results.json
```

View File

@@ -0,0 +1,20 @@
# Prometheus Kubescape Integration
1. Deploy kubescape
```bash
kubectl apply -f ks-deployment.yaml
```
> **NOTE** Make sure the configurations suit your cluster (e.g. `serviceType`, etc.)
2. Deploy kube-prometheus-stack
```bash
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo update
kubectl create namespace prometheus
helm install -n prometheus kube-prometheus-stack prometheus-community/kube-prometheus-stack --set prometheus.prometheusSpec.podMonitorSelectorNilUsesHelmValues=false,prometheus.prometheusSpec.serviceMonitorSelectorNilUsesHelmValues=false
```
3. Deploy pod monitor
```bash
kubectl apply -f podmonitor.yaml
```

View File

@@ -51,6 +51,7 @@ spec:
type: NodePort
ports:
- port: 8080
name: http
targetPort: 8080
protocol: TCP
selector:
@@ -76,26 +77,35 @@ spec:
serviceAccountName: kubescape-discovery
containers:
- name: kubescape
# livenessProbe:
# httpGet:
# path: /livez
# port: 8080
# initialDelaySeconds: 3
# periodSeconds: 3
# readinessProbe:
# httpGet:
# path: /readyz
# port: 8080
# initialDelaySeconds: 3
# periodSeconds: 3
image: quay.io/armosec/kubescape:prometheus.v1
livenessProbe:
httpGet:
path: /livez
port: 8080
initialDelaySeconds: 3
periodSeconds: 3
readinessProbe:
httpGet:
path: /readyz
port: 8080
initialDelaySeconds: 3
periodSeconds: 3
image: quay.io/armosec/kubescape:prometheus.v2
env:
- name: KS_RUN_PROMETHEUS_SERVER
value: "true"
- name: KS_DEFAULT_CONFIGMAP_NAMESPACE
value: "ks-scanner"
valueFrom:
fieldRef:
apiVersion: v1
fieldPath: metadata.namespace
- name: "KS_SKIP_UPDATE_CHECK" # do not check latest version
value: "true"
- name: KS_ENABLE_HOST_SCANNER # enable host scanner -> https://hub.armo.cloud/docs/host-sensor
value: "false" # TODO - add permissions to rbac
- name: KS_DOWNLOAD_ARTIFACTS # When set to true the artifacts will be downloaded every scan execution
value: "false"
ports:
- containerPort: 8080
name: http
protocol: TCP
command:
- kubescape
resources:

View File

@@ -0,0 +1,16 @@
apiVersion: monitoring.coreos.com/v1
kind: PodMonitor
metadata:
name: kubescape
namespace: ks-scanner
labels:
app: kubescape
spec:
selector:
matchLabels:
app: kubescape
podMetricsEndpoints:
- port: http
# path: v1
interval: 120s
scrapeTimeout: 100s

View File

@@ -6,8 +6,10 @@ replace github.com/armosec/kubescape/core => ../core
require (
github.com/armosec/kubescape/core v0.0.0-00010101000000-000000000000
github.com/armosec/utils-go v0.0.3
github.com/google/uuid v1.3.0
github.com/gorilla/mux v1.8.0
github.com/stretchr/testify v1.7.0
)
require (
@@ -22,9 +24,8 @@ require (
github.com/OneOfOne/xxhash v1.2.8 // indirect
github.com/armosec/armoapi-go v0.0.58 // indirect
github.com/armosec/k8s-interface v0.0.68 // indirect
github.com/armosec/opa-utils v0.0.116 // indirect
github.com/armosec/opa-utils v0.0.118 // indirect
github.com/armosec/rbac-utils v0.0.14 // indirect
github.com/armosec/utils-go v0.0.3 // indirect
github.com/armosec/utils-k8s-go v0.0.3 // indirect
github.com/aws/aws-sdk-go v1.41.11 // indirect
github.com/aws/aws-sdk-go-v2 v1.12.0 // indirect
@@ -83,6 +84,7 @@ require (
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.0.2 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/pquerna/cachecontrol v0.1.0 // indirect
github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0 // indirect
github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58 // indirect

View File

@@ -109,8 +109,8 @@ github.com/armosec/k8s-interface v0.0.66/go.mod h1:vwprS8qn/iowd5yf0JHpqDsLA5I8W
github.com/armosec/k8s-interface v0.0.68 h1:6CtSakISiI47YHkxh+Va9FzZQIBkWa6g9sbiNxq1Zkk=
github.com/armosec/k8s-interface v0.0.68/go.mod h1:PeWn41C2uenZi+xfZdyFF/zG5wXACA00htQyknDUWDE=
github.com/armosec/opa-utils v0.0.64/go.mod h1:6tQP8UDq2EvEfSqh8vrUdr/9QVSCG4sJfju1SXQOn4c=
github.com/armosec/opa-utils v0.0.116 h1:3oWuhcpI+MJD/CktEStU1BA0feGNwsCbQrI3ifVfzMs=
github.com/armosec/opa-utils v0.0.116/go.mod h1:gap+EaLG5rnyqvIRGxtdNDC9y7VvoGNm90zK8Ls7avQ=
github.com/armosec/opa-utils v0.0.118 h1:ZX1crwVQmo+sDv+jmTNLbDYfApUBzlgPhD8QI2GCJX0=
github.com/armosec/opa-utils v0.0.118/go.mod h1:gap+EaLG5rnyqvIRGxtdNDC9y7VvoGNm90zK8Ls7avQ=
github.com/armosec/rbac-utils v0.0.1/go.mod h1:pQ8CBiij8kSKV7aeZm9FMvtZN28VgA7LZcYyTWimq40=
github.com/armosec/rbac-utils v0.0.14 h1:CKYKcgqJEXWF2Hen/B1pVGtS3nDAG1wp9dDv6oNtq90=
github.com/armosec/rbac-utils v0.0.14/go.mod h1:Ex/IdGWhGv9HZq6Hs8N/ApzCKSIvpNe/ETqDfnuyah0=

View File

@@ -9,6 +9,7 @@ type PostScanRequest struct {
HostScanner bool `json:"hostScanner"` // Deploy ARMO K8s host scanner to collect data from certain controls
KeepLocal bool `json:"keepLocal"` // Do not submit results
Account string `json:"account"` // account ID
UseCachedArtifacts bool `json:"useCachedArtifacts"` // Use the cached artifacts instead of downloading
Logger string `json:"-"` // logger level - debug/info/error - default is debug
TargetType string `json:"-"` // framework/control - default is framework
TargetNames []string `json:"-"` // default is all

View File

@@ -4,6 +4,7 @@ import (
"strings"
"github.com/armosec/kubescape/core/cautils"
"github.com/armosec/kubescape/core/cautils/getter"
)
func (scanRequest *PostScanRequest) ToScanInfo() *cautils.ScanInfo {
@@ -15,6 +16,10 @@ func (scanRequest *PostScanRequest) ToScanInfo() *cautils.ScanInfo {
scanInfo.Format = scanRequest.Format // TODO - handle default
if scanRequest.UseCachedArtifacts {
scanInfo.UseArtifactsFrom = getter.DefaultLocalStore // Load files from cache (this will prevent kubescape fom downloading the artifacts every time)
}
scanInfo.Local = scanRequest.KeepLocal
scanInfo.Submit = scanRequest.Submit
scanInfo.HostSensorEnabled.SetBool(scanRequest.HostScanner)

View File

@@ -15,45 +15,52 @@ import (
// Metrics http listener for prometheus support
func (handler *HTTPHandler) Metrics(w http.ResponseWriter, r *http.Request) {
if handler.state.isBusy() { // if already scanning the cluster
w.Write([]byte(fmt.Sprintf("scan '%s' in action", handler.state.getID())))
message := fmt.Sprintf("scan '%s' in action", handler.state.getID())
logger.L().Info("server is busy", helpers.String("message", message), helpers.Time())
w.WriteHeader(http.StatusServiceUnavailable)
w.Write([]byte(message))
return
}
handler.state.setBusy()
defer handler.state.setNotBusy()
handler.state.setID(uuid.NewString())
scanID := uuid.NewString()
handler.state.setID(scanID)
// trigger scanning
logger.L().Info(handler.state.getID(), helpers.String("action", "triggering scan"), helpers.Time())
ks := core.NewKubescape()
results, err := ks.Scan(getPrometheusDefaultScanCommand(handler.state.getID()))
results, err := ks.Scan(getPrometheusDefaultScanCommand(scanID))
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(fmt.Sprintf("failed to complete scan. reason: %s", err.Error())))
return
}
results.HandleResults()
logger.L().Info(handler.state.getID(), helpers.String("action", "done scanning"), helpers.Time())
f, err := os.ReadFile(scanID)
// res, err := results.ToJson()
if err != nil {
w.Write([]byte(fmt.Sprintf("failed to complete scan. reason: %s", err.Error())))
w.WriteHeader(http.StatusInternalServerError)
return
}
res, err := results.ToJson()
if err != nil {
w.Write([]byte(fmt.Sprintf("failed to convert scan scan results to json. reason: %s", err.Error())))
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(fmt.Sprintf("failed read results from file. reason: %s", err.Error())))
return
}
os.Remove(scanID)
w.Write(res)
w.WriteHeader(http.StatusOK)
w.Write(f)
}
func getPrometheusDefaultScanCommand(scanID string) *cautils.ScanInfo {
scanInfo := cautils.ScanInfo{}
scanInfo := defaultScanInfo()
scanInfo.FrameworkScan = true
scanInfo.ScanAll = true // scan all frameworks
scanInfo.ReportID = scanID // scan ID
scanInfo.HostSensorEnabled.Set(os.Getenv("KS_ENABLE_HOST_SENSOR")) // enable host scanner
scanInfo.FailThreshold = 100 // Do not fail scanning
// scanInfo.Format = "prometheus" // results format
return &scanInfo
scanInfo.ScanAll = true // scan all frameworks
scanInfo.ReportID = scanID // scan ID
scanInfo.FailThreshold = 100 // Do not fail scanning
scanInfo.Output = scanID // results output
scanInfo.Format = envToString("KS_FORMAT", "prometheus") // default output should be json
scanInfo.HostSensorEnabled.SetBool(envToBool("KS_ENABLE_HOST_SCANNER", false)) // enable host scanner
return scanInfo
}

View File

@@ -0,0 +1,19 @@
package v1
import (
"testing"
"github.com/armosec/kubescape/core/cautils/getter"
"github.com/stretchr/testify/assert"
)
func TestGetPrometheusDefaultScanCommand(t *testing.T) {
scanID := "1234"
scanInfo := getPrometheusDefaultScanCommand(scanID)
assert.Equal(t, scanID, scanInfo.ReportID)
assert.Equal(t, scanID, scanInfo.Output)
assert.Equal(t, "prometheus", scanInfo.Format)
// assert.False(t, *scanInfo.HostSensorEnabled.Get())
assert.Equal(t, getter.DefaultLocalStore, scanInfo.UseArtifactsFrom)
}

View File

@@ -128,7 +128,6 @@ func (handler *HTTPHandler) Results(w http.ResponseWriter, r *http.Request) {
if scanID = r.URL.Query().Get("scanID"); scanID == "" {
scanID = handler.state.getLatestID()
}
logger.L().Info("requesting results", helpers.String("ID", scanID))
if handler.state.isBusy() { // if requested ID is still scanning
if scanID == handler.state.getID() {
@@ -141,6 +140,8 @@ func (handler *HTTPHandler) Results(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodGet:
logger.L().Info("requesting results", helpers.String("ID", scanID))
if r.URL.Query().Has("remove") {
defer removeResultsFile(scanID)
}
@@ -152,6 +153,8 @@ func (handler *HTTPHandler) Results(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
}
case http.MethodDelete:
logger.L().Info("deleting results", helpers.String("ID", scanID))
if r.URL.Query().Has("all") {
removeResultDirs()
} else {

View File

@@ -5,12 +5,16 @@ import (
"os"
"path/filepath"
pkgcautils "github.com/armosec/utils-go/utils"
"github.com/armosec/kubescape/core/cautils"
"github.com/armosec/kubescape/core/cautils/getter"
"github.com/armosec/kubescape/core/core"
)
func scan(scanRequest *PostScanRequest, scanID string) ([]byte, error) {
scanInfo := getScanCommand(scanRequest, scanID)
ks := core.NewKubescape()
result, err := ks.Scan(scanInfo)
if err != nil {
@@ -34,7 +38,7 @@ func readResultsFile(fileID string) ([]byte, error) {
if fileName := searchFile(fileID); fileName != "" {
return os.ReadFile(fileName)
}
return nil, fmt.Errorf("file not found")
return nil, fmt.Errorf("file %s not found", fileID)
}
func removeResultDirs() {
@@ -59,7 +63,7 @@ func searchFile(fileID string) string {
func findFile(targetDir string, fileName string) (string, error) {
matches, err := filepath.Glob(targetDir + fileName)
matches, err := filepath.Glob(filepath.Join(targetDir, fileName))
if err != nil {
return "", err
}
@@ -94,7 +98,35 @@ func getScanCommand(scanRequest *PostScanRequest, scanID string) *cautils.ScanIn
scanInfo.Output = filepath.Join(OutputDir, scanID)
// *** end ***
scanInfo.Init()
return scanInfo
}
func defaultScanInfo() *cautils.ScanInfo {
scanInfo := &cautils.ScanInfo{}
scanInfo.Account = envToString("KS_ACCOUNT", "") // publish results to Kubescape SaaS
scanInfo.ExcludedNamespaces = envToString("KS_EXCLUDE_NAMESPACES", "") // namespace to exclude
scanInfo.IncludeNamespaces = envToString("KS_INCLUDE_NAMESPACES", "") // namespace to include
scanInfo.FormatVersion = envToString("KS_FORMAT_VERSION", "v2") // output format version
scanInfo.Format = envToString("KS_FORMAT", "json") // default output should be json
scanInfo.Submit = envToBool("KS_SUBMIT", false) // publish results to Kubescape SaaS
scanInfo.HostSensorEnabled.SetBool(envToBool("KS_ENABLE_HOST_SCANNER", true)) // enable host scanner
scanInfo.Local = envToBool("KS_KEEP_LOCAL", false) // do not publish results to Kubescape SaaS
if !envToBool("KS_DOWNLOAD_ARTIFACTS", false) {
scanInfo.UseArtifactsFrom = getter.DefaultLocalStore // Load files from cache (this will prevent kubescape fom downloading the artifacts every time)
}
return scanInfo
}
func envToBool(env string, defaultValue bool) bool {
if d, ok := os.LookupEnv(env); ok {
return pkgcautils.StringToBool(d)
}
return defaultValue
}
func envToString(env string, defaultValue string) string {
if d, ok := os.LookupEnv(env); ok {
return d
}
return defaultValue
}

View File

@@ -6,8 +6,10 @@ import (
"net/http"
"os"
"github.com/armosec/kubescape/core/cautils"
"github.com/armosec/kubescape/core/cautils/logger"
"github.com/armosec/kubescape/core/cautils/logger/helpers"
"github.com/armosec/kubescape/core/cautils/logger/zaplogger"
handlerequestsv1 "github.com/armosec/kubescape/httphandler/handlerequests/v1"
"github.com/gorilla/mux"
)
@@ -15,13 +17,15 @@ import (
const (
scanPath = "/v1/scan"
resultsPath = "/v1/results"
prometheusMmeticsPath = "/v1/metrics"
prometheusMmeticsPath = "/metrics"
livePath = "/livez"
readyPath = "/readyz"
)
// SetupHTTPListener set up listening http servers
func SetupHTTPListener() error {
logger.InitLogger(zaplogger.LoggerName)
keyPair, err := loadTLSKey("", "") // TODO - support key and crt files
if err != nil {
return err
@@ -47,7 +51,7 @@ func SetupHTTPListener() error {
server.Handler = rtr
logger.L().Info("Started Kubescape server", helpers.String("port", getPort()))
logger.L().Info("Started Kubescape server", helpers.String("port", getPort()), helpers.String("version", cautils.BuildNumber))
server.ListenAndServe()
if keyPair != nil {
return server.ListenAndServeTLS("", "")