mirror of
https://github.com/kubescape/kubescape.git
synced 2026-02-24 23:04:04 +00:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8f9b46cdbe | ||
|
|
06a2fa05be | ||
|
|
d26f90b98e | ||
|
|
9d957b3c77 | ||
|
|
8ec5615569 | ||
|
|
6477437872 | ||
|
|
6099f46dea |
2
.github/workflows/build.yaml
vendored
2
.github/workflows/build.yaml
vendored
@@ -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:
|
||||
|
||||
2
.github/workflows/build_dev.yaml
vendored
2
.github/workflows/build_dev.yaml
vendored
@@ -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:
|
||||
|
||||
12
.github/workflows/master_pr_checks.yaml
vendored
12
.github/workflows/master_pr_checks.yaml
vendored
@@ -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:
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -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))
|
||||
@@ -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
|
||||
|
||||
@@ -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=
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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=
|
||||
|
||||
@@ -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})}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
55
core/pkg/resultshandling/printer/v2/prometheus.go
Normal file
55
core/pkg/resultshandling/printer/v2/prometheus.go
Normal 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))
|
||||
}
|
||||
}
|
||||
252
core/pkg/resultshandling/printer/v2/prometheusutils.go
Normal file
252
core/pkg/resultshandling/printer/v2/prometheusutils.go
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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:
|
||||
|
||||
@@ -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
92
httphandler/build.py
Normal 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()
|
||||
20
httphandler/examples/microservice/README.md
Normal file
20
httphandler/examples/microservice/README.md
Normal 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
|
||||
```
|
||||
20
httphandler/examples/prometheus/README.md
Normal file
20
httphandler/examples/prometheus/README.md
Normal 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
|
||||
```
|
||||
|
||||
@@ -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:
|
||||
16
httphandler/examples/prometheus/podmonitor.yaml
Normal file
16
httphandler/examples/prometheus/podmonitor.yaml
Normal 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
|
||||
@@ -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
|
||||
|
||||
@@ -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=
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
19
httphandler/handlerequests/v1/prometheus_test.go
Normal file
19
httphandler/handlerequests/v1/prometheus_test.go
Normal 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)
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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("", "")
|
||||
|
||||
Reference in New Issue
Block a user