mirror of
https://github.com/kubescape/kubescape.git
synced 2026-04-15 06:58:11 +00:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ac5e7069da | ||
|
|
5a83f38bca | ||
|
|
3abd59e290 | ||
|
|
d08fdf2e9e | ||
|
|
fc9b713851 | ||
|
|
3f87610e8c | ||
|
|
63968b564b |
24
.github/workflows/build.yaml
vendored
24
.github/workflows/build.yaml
vendored
@@ -101,28 +101,28 @@ jobs:
|
||||
id: image-name
|
||||
run: echo '::set-output name=IMAGE_NAME::quay.io/${{ github.repository_owner }}/kubescape'
|
||||
|
||||
- name: Build the Docker image
|
||||
run: docker build . --file build/Dockerfile --tag ${{ steps.image-name.outputs.IMAGE_NAME }}:${{ steps.image-version.outputs.IMAGE_VERSION }} --build-arg image_version=${{ steps.image-version.outputs.IMAGE_VERSION }}
|
||||
|
||||
- name: Re-Tag Image to latest
|
||||
run: docker tag ${{ steps.image-name.outputs.IMAGE_NAME }}:${{ steps.image-version.outputs.IMAGE_VERSION }} ${{ steps.image-name.outputs.IMAGE_NAME }}:latest
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
|
||||
- name: Login to Quay.io
|
||||
env: # Or as an environment variable
|
||||
env:
|
||||
QUAY_PASSWORD: ${{ secrets.QUAYIO_REGISTRY_PASSWORD }}
|
||||
QUAY_USERNAME: ${{ secrets.QUAYIO_REGISTRY_USERNAME }}
|
||||
run: docker login -u="${QUAY_USERNAME}" -p="${QUAY_PASSWORD}" quay.io
|
||||
|
||||
- name: Build the Docker image
|
||||
run: docker buildx build . --file build/Dockerfile --tag ${{ steps.image-name.outputs.IMAGE_NAME }}:${{ steps.image-version.outputs.IMAGE_VERSION }} --tag ${{ steps.image-name.outputs.IMAGE_NAME }}:latest --build-arg image_version=${{ steps.image-version.outputs.IMAGE_VERSION }} --push --platform linux/amd64,linux/arm64
|
||||
|
||||
# - name: Login to GitHub Container Registry
|
||||
# uses: docker/login-action@v1
|
||||
# with:
|
||||
# registry: ghcr.io
|
||||
# username: ${{ github.actor }}
|
||||
# password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Push Docker image
|
||||
run: |
|
||||
docker push ${{ steps.image-name.outputs.IMAGE_NAME }}:${{ steps.image-version.outputs.IMAGE_VERSION }}
|
||||
docker push ${{ steps.image-name.outputs.IMAGE_NAME }}:latest
|
||||
|
||||
|
||||
# TODO - Wait for casign to support fixed tags -> https://github.com/sigstore/cosign/issues/1424
|
||||
# - name: Install cosign
|
||||
# uses: sigstore/cosign-installer@main
|
||||
|
||||
22
.github/workflows/build_dev.yaml
vendored
22
.github/workflows/build_dev.yaml
vendored
@@ -80,21 +80,17 @@ jobs:
|
||||
id: image-name
|
||||
run: echo '::set-output name=IMAGE_NAME::quay.io/${{ github.repository_owner }}/kubescape'
|
||||
|
||||
- name: Build the Docker image
|
||||
run: docker build . --file build/Dockerfile --tag ${{ steps.image-name.outputs.IMAGE_NAME }}:${{ steps.image-version.outputs.IMAGE_VERSION }} --build-arg image_version=${{ steps.image-version.outputs.IMAGE_VERSION }}
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
|
||||
- name: Login to Quay.io
|
||||
env:
|
||||
QUAY_PASSWORD: ${{ secrets.QUAYIO_REGISTRY_PASSWORD }}
|
||||
QUAY_USERNAME: ${{ secrets.QUAYIO_REGISTRY_USERNAME }}
|
||||
run: docker login -u="${QUAY_USERNAME}" -p="${QUAY_PASSWORD}" quay.io
|
||||
# - name: Login to GitHub Container Registry
|
||||
# uses: docker/login-action@v1
|
||||
# with:
|
||||
# registry: ghcr.io
|
||||
# username: ${{ github.actor }}
|
||||
# password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Push Docker image
|
||||
run: |
|
||||
docker push ${{ steps.image-name.outputs.IMAGE_NAME }}:${{ steps.image-version.outputs.IMAGE_VERSION }}
|
||||
|
||||
|
||||
- name: Build the Docker image
|
||||
run: docker buildx build . --file build/Dockerfile --tag ${{ steps.image-name.outputs.IMAGE_NAME }}:${{ steps.image-version.outputs.IMAGE_VERSION }} --build-arg image_version=${{ steps.image-version.outputs.IMAGE_VERSION }} --push --platform linux/amd64,linux/arm64
|
||||
|
||||
4
go.mod
4
go.mod
@@ -5,8 +5,8 @@ go 1.17
|
||||
require (
|
||||
github.com/armosec/armoapi-go v0.0.73
|
||||
github.com/armosec/go-git-url v0.0.4
|
||||
github.com/armosec/opa-utils v0.0.139
|
||||
github.com/armosec/k8s-interface v0.0.75
|
||||
github.com/armosec/k8s-interface v0.0.76
|
||||
github.com/armosec/opa-utils v0.0.140
|
||||
github.com/armosec/rbac-utils v0.0.14
|
||||
github.com/armosec/utils-go v0.0.5
|
||||
github.com/armosec/utils-k8s-go v0.0.6
|
||||
|
||||
9
go.sum
9
go.sum
@@ -119,12 +119,11 @@ github.com/armosec/go-git-url v0.0.4 h1:emG9Yfl53rHpuX41fXLD92ehzhRoNSSnGT6Pr7og
|
||||
github.com/armosec/go-git-url v0.0.4/go.mod h1:PJqdEyJyFxTQvawBcyOM0Ies6+uezire5gpwfr1XX5M=
|
||||
github.com/armosec/k8s-interface v0.0.8/go.mod h1:xxS+V5QT3gVQTwZyAMMDrYLWGrfKOpiJ7Jfhfa0w9sM=
|
||||
github.com/armosec/k8s-interface v0.0.37/go.mod h1:vHxGWqD/uh6+GQb9Sqv7OGMs+Rvc2dsFVc0XtgRh1ZU=
|
||||
github.com/armosec/k8s-interface v0.0.70/go.mod h1:8NX4xWXh8mwW7QyZdZea1czNdM2azCK9BbUNmiZYXW0=
|
||||
github.com/armosec/k8s-interface v0.0.75 h1:pfheXWGcE6vUlo4TOkwXQ8iGo8Dw/UCXefD3Bx4l0Qs=
|
||||
github.com/armosec/k8s-interface v0.0.75/go.mod h1:8NX4xWXh8mwW7QyZdZea1czNdM2azCK9BbUNmiZYXW0=
|
||||
github.com/armosec/k8s-interface v0.0.76 h1:pQaF+8BcNMm6GTYTjdG7vCM1l4BIk7oALXoT6v5gCAk=
|
||||
github.com/armosec/k8s-interface v0.0.76/go.mod h1:8NX4xWXh8mwW7QyZdZea1czNdM2azCK9BbUNmiZYXW0=
|
||||
github.com/armosec/opa-utils v0.0.64/go.mod h1:6tQP8UDq2EvEfSqh8vrUdr/9QVSCG4sJfju1SXQOn4c=
|
||||
github.com/armosec/opa-utils v0.0.139 h1:JPxgPXVJUUIujtIoZk6TejE8PkZhX2pYnpj+E8PhcfA=
|
||||
github.com/armosec/opa-utils v0.0.139/go.mod h1:VnRVJgDDPFAprGDcibTtKHf9wgkoyTU8wmX2BxEIwok=
|
||||
github.com/armosec/opa-utils v0.0.140 h1:iv6inb6+D0qgeVkv7f+ZIHpy239IUpAwg6Dau0JAWzg=
|
||||
github.com/armosec/opa-utils v0.0.140/go.mod h1:Hwm9ZkcW87mB2567WT6mBuSBEzaKowBNfrl3Q0IVsV8=
|
||||
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=
|
||||
|
||||
@@ -1,26 +1,85 @@
|
||||
# Kubescape HTTP Handler Package
|
||||
|
||||
> This is a beta version, we might make some changes before publishing the official Prometheus support
|
||||
|
||||
Running `kubescape` will start up a webserver on port `8080` which will serve the following paths:
|
||||
Running `kubescape` will start up a webserver on port `8080` which will serve the following API's:
|
||||
|
||||
### Trigger scan
|
||||
|
||||
* POST `/v1/scan` - Trigger a kubescape scan. The server will return an ID and will execute the scanning asynchronously
|
||||
* * `wait`: scan synchronously (return results and not ID). Use only in small clusters are with an increased timeout
|
||||
* * `keep`: Do not delete results from local storage after returning
|
||||
* POST `/v1/scan` - trigger a kubescape scan. The server will return an ID and will execute the scanning asynchronously. the request body should look [as followed](#trigger-scan-object).
|
||||
* * `wait=true`: scan synchronously (return results and not ID). Use only in small clusters or with an increased timeout. default is `wait=false`
|
||||
* * `keep=true`: do not delete results from local storage after returning. default is `keep=false`
|
||||
* POST `/v1/metrics` - trigger kubescape for Prometheus support. [read more](examples/prometheus/README.md)
|
||||
|
||||
[Response](#response-object):
|
||||
|
||||
```
|
||||
{
|
||||
"id": <str>, // scan ID
|
||||
"type": "busy", // response object type
|
||||
"response": <message:string> // message indicating scanning is still in process
|
||||
}
|
||||
```
|
||||
|
||||
> When scanning was triggered with the `wait=true` query param, the response is like the [`/v1/results` API](#get-results) response
|
||||
|
||||
### Get results
|
||||
* GET `/v1/results` - Request kubescape scan results
|
||||
* * query `id=<string>` -> ID returned when triggering the scan action. If empty will return latest results
|
||||
* * query `keep` -> Do not delete results from local storage after returning
|
||||
* GET `/v1/results` - request kubescape scan results
|
||||
* * query `id=<string>` -> request results of a specific scan ID. If empty will return latest results
|
||||
* * query `keep=true` -> keep the results in the local storage after returning. default is `keep=false` - the results will be deleted from local storage after they are returned
|
||||
|
||||
[Response](#response-object):
|
||||
|
||||
When scanning was done successfully
|
||||
```
|
||||
{
|
||||
"id": <str>, // scan ID
|
||||
"type": "v1results", // response object type
|
||||
"response": <object:v1results> // v1 results payload
|
||||
}
|
||||
```
|
||||
|
||||
When scanning failed
|
||||
```
|
||||
{
|
||||
"id": <str>, // scan ID
|
||||
"type": "error", // response object type
|
||||
"response": <error:string> // error string
|
||||
}
|
||||
```
|
||||
|
||||
When scanning is in progress
|
||||
```
|
||||
{
|
||||
"id": <str>, // scan ID
|
||||
"type": "busy", // response object type
|
||||
"response": <message:string> // message indicating scanning is still in process
|
||||
}
|
||||
```
|
||||
### Check scanning progress status
|
||||
Check the scanning status - is the scanning in progress or done. This is meant for a waiting mechanize since the API does not return the entire results object when the scanning is done
|
||||
|
||||
* GET `/v1/status` - Request kubescape scan status
|
||||
* * query `id=<string>` -> Check status of a specific scan. If empty will check if any scan is in progress
|
||||
|
||||
[Response](#response-object):
|
||||
|
||||
When scanning is in progress
|
||||
```
|
||||
{
|
||||
"id": <str>, // scan ID
|
||||
"type": "busy", // response object type
|
||||
"response": <message:string> // message indicating scanning is still in process
|
||||
}
|
||||
```
|
||||
|
||||
When scanning is not in progress
|
||||
```
|
||||
{
|
||||
"id": <str>, // scan ID
|
||||
"type": "notBusy", // response object type
|
||||
"response": <message:string> // message indicating scanning is done in process
|
||||
}
|
||||
```
|
||||
|
||||
### Delete cached results
|
||||
* DELETE `/v1/results` - Delete kubescape scan results from storage. If empty will delete latest results
|
||||
* * query `id=<string>`: Delete ID of specific results
|
||||
@@ -32,10 +91,10 @@ Check the scanning status - is the scanning in progress or done. This is meant f
|
||||
* `/livez` - will respond 200 is server is alive
|
||||
* `/readyz` - will respond 200 if server can receive requests
|
||||
|
||||
## Trigger Kubescape scan
|
||||
## Objects
|
||||
|
||||
### Trigger scan object
|
||||
|
||||
POST /v1/scan
|
||||
body:
|
||||
```
|
||||
{
|
||||
"format": <str>, // results format [default: json] (same as 'kubescape scan --format')
|
||||
@@ -51,7 +110,8 @@ body:
|
||||
}
|
||||
```
|
||||
|
||||
Response body:
|
||||
### Response object
|
||||
|
||||
```
|
||||
{
|
||||
"id": <str>, // scan ID
|
||||
@@ -59,10 +119,12 @@ Response body:
|
||||
"response": <object:interface> // response payload as list of bytes
|
||||
}
|
||||
```
|
||||
#### Response object types
|
||||
|
||||
Response body types:
|
||||
* "v1results" - v1 results object
|
||||
* "id" - id string
|
||||
* "busy" - server is busy processing previous requests
|
||||
* "notBusy" - server is not busy processing previous requests
|
||||
* "ready" - server is done processing request and results are ready
|
||||
* "error" - error object
|
||||
|
||||
## API Examples
|
||||
|
||||
@@ -110,13 +110,4 @@ kubescape_control_count_resources_excluded{name="<control name>",url="<docs url>
|
||||
kubescape_control_count_resources_passed{name="<control name>",url="<docs url>",severity="<control severity>"} <counter>
|
||||
```
|
||||
|
||||
#### Resources metrics
|
||||
The resources metrics give you the ability to prioritize fixing the resources by the number of controls that failed
|
||||
|
||||
```
|
||||
# Number of controls that failed for this particular resource
|
||||
kubescape_resource_count_controls_failed{apiVersion="<>",kind="<>",namespace="<>",name="<>"} <counter>
|
||||
|
||||
# Number of controls that where excluded for this particular resource
|
||||
kubescape_resource_count_controls_excluded{apiVersion="<>",kind="<>",namespace="<>",name="<>"} <counter>
|
||||
```
|
||||
|
||||
@@ -6,7 +6,7 @@ replace github.com/armosec/kubescape/v2 => ../
|
||||
|
||||
require (
|
||||
github.com/armosec/kubescape/v2 v2.0.0-00010101000000-000000000000
|
||||
github.com/armosec/opa-utils v0.0.139
|
||||
github.com/armosec/opa-utils v0.0.140
|
||||
github.com/armosec/utils-go v0.0.5
|
||||
github.com/google/uuid v1.3.0
|
||||
github.com/gorilla/mux v1.8.0
|
||||
@@ -31,7 +31,7 @@ require (
|
||||
github.com/OneOfOne/xxhash v1.2.8 // indirect
|
||||
github.com/armosec/armoapi-go v0.0.73 // indirect
|
||||
github.com/armosec/go-git-url v0.0.4 // indirect
|
||||
github.com/armosec/k8s-interface v0.0.75 // indirect
|
||||
github.com/armosec/k8s-interface v0.0.76 // indirect
|
||||
github.com/armosec/rbac-utils v0.0.14 // indirect
|
||||
github.com/armosec/utils-k8s-go v0.0.6 // indirect
|
||||
github.com/aws/aws-sdk-go v1.41.11 // indirect
|
||||
|
||||
@@ -113,7 +113,6 @@ github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj
|
||||
github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
||||
github.com/armosec/armoapi-go v0.0.2/go.mod h1:vIK17yoKbJRQyZXWWLe3AqfqCRITxW8qmSkApyq5xFs=
|
||||
github.com/armosec/armoapi-go v0.0.23/go.mod h1:iaVVGyc23QGGzAdv4n+szGQg3Rbpixn9yQTU3qWRpaw=
|
||||
github.com/armosec/armoapi-go v0.0.67/go.mod h1:/9SQAgtLbYkfFneRRm/zkIn3zz+4Y2xv6N3vtFcyF8s=
|
||||
github.com/armosec/armoapi-go v0.0.73 h1:LMf+eCkkf+W9NVvOzHKFgVUEpBMvh27M7//UQP3aiO8=
|
||||
github.com/armosec/armoapi-go v0.0.73/go.mod h1:/9SQAgtLbYkfFneRRm/zkIn3zz+4Y2xv6N3vtFcyF8s=
|
||||
github.com/armosec/go-git-url v0.0.4 h1:emG9Yfl53rHpuX41fXLD92ehzhRoNSSnGT6Pr7ogWMY=
|
||||
@@ -121,12 +120,12 @@ github.com/armosec/go-git-url v0.0.4/go.mod h1:PJqdEyJyFxTQvawBcyOM0Ies6+uezire5
|
||||
github.com/armosec/k8s-interface v0.0.8/go.mod h1:xxS+V5QT3gVQTwZyAMMDrYLWGrfKOpiJ7Jfhfa0w9sM=
|
||||
github.com/armosec/k8s-interface v0.0.37/go.mod h1:vHxGWqD/uh6+GQb9Sqv7OGMs+Rvc2dsFVc0XtgRh1ZU=
|
||||
github.com/armosec/k8s-interface v0.0.70/go.mod h1:8NX4xWXh8mwW7QyZdZea1czNdM2azCK9BbUNmiZYXW0=
|
||||
github.com/armosec/k8s-interface v0.0.75 h1:pfheXWGcE6vUlo4TOkwXQ8iGo8Dw/UCXefD3Bx4l0Qs=
|
||||
github.com/armosec/k8s-interface v0.0.75/go.mod h1:8NX4xWXh8mwW7QyZdZea1czNdM2azCK9BbUNmiZYXW0=
|
||||
github.com/armosec/k8s-interface v0.0.76 h1:pQaF+8BcNMm6GTYTjdG7vCM1l4BIk7oALXoT6v5gCAk=
|
||||
github.com/armosec/k8s-interface v0.0.76/go.mod h1:8NX4xWXh8mwW7QyZdZea1czNdM2azCK9BbUNmiZYXW0=
|
||||
github.com/armosec/opa-utils v0.0.64/go.mod h1:6tQP8UDq2EvEfSqh8vrUdr/9QVSCG4sJfju1SXQOn4c=
|
||||
github.com/armosec/opa-utils v0.0.137/go.mod h1:mCFQzz4E227f7V2jQVQ9XCivkNNK3UWCTaZ0HE5rBWk=
|
||||
github.com/armosec/opa-utils v0.0.139 h1:JPxgPXVJUUIujtIoZk6TejE8PkZhX2pYnpj+E8PhcfA=
|
||||
github.com/armosec/opa-utils v0.0.139/go.mod h1:VnRVJgDDPFAprGDcibTtKHf9wgkoyTU8wmX2BxEIwok=
|
||||
github.com/armosec/opa-utils v0.0.140 h1:iv6inb6+D0qgeVkv7f+ZIHpy239IUpAwg6Dau0JAWzg=
|
||||
github.com/armosec/opa-utils v0.0.140/go.mod h1:Hwm9ZkcW87mB2567WT6mBuSBEzaKowBNfrl3Q0IVsV8=
|
||||
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=
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
|
||||
apisv1 "github.com/armosec/opa-utils/httpserver/apis/v1"
|
||||
utilsmetav1 "github.com/armosec/opa-utils/httpserver/meta/v1"
|
||||
"k8s.io/utils/strings/slices"
|
||||
|
||||
"github.com/armosec/kubescape/v2/core/cautils"
|
||||
"github.com/armosec/kubescape/v2/core/cautils/getter"
|
||||
@@ -59,6 +60,9 @@ func ToScanInfo(scanRequest *utilsmetav1.PostScanRequest) *cautils.ScanInfo {
|
||||
}
|
||||
|
||||
func setTargetInScanInfo(scanRequest *utilsmetav1.PostScanRequest, scanInfo *cautils.ScanInfo) {
|
||||
// remove empty targets from slice
|
||||
scanRequest.TargetNames = slices.Filter(nil, scanRequest.TargetNames, func(e string) bool { return e != "" })
|
||||
|
||||
if scanRequest.TargetType != "" && len(scanRequest.TargetNames) > 0 {
|
||||
if strings.EqualFold(string(scanRequest.TargetType), string(apisv1.KindFramework)) {
|
||||
scanRequest.TargetType = apisv1.KindFramework
|
||||
|
||||
@@ -61,6 +61,17 @@ func TestToScanInfo(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestSetTargetInScanInfo(t *testing.T) {
|
||||
{
|
||||
req := &utilsmetav1.PostScanRequest{
|
||||
TargetType: apisv1.KindFramework,
|
||||
TargetNames: []string{""},
|
||||
}
|
||||
scanInfo := &cautils.ScanInfo{}
|
||||
setTargetInScanInfo(req, scanInfo)
|
||||
assert.True(t, scanInfo.FrameworkScan)
|
||||
assert.True(t, scanInfo.ScanAll)
|
||||
assert.Equal(t, 0, len(scanInfo.PolicyIdentifier))
|
||||
}
|
||||
{
|
||||
req := &utilsmetav1.PostScanRequest{
|
||||
TargetType: apisv1.KindFramework,
|
||||
|
||||
@@ -7,49 +7,56 @@ import (
|
||||
"path/filepath"
|
||||
|
||||
"github.com/armosec/kubescape/v2/core/cautils"
|
||||
"github.com/armosec/kubescape/v2/core/cautils/logger"
|
||||
"github.com/armosec/kubescape/v2/core/cautils/logger/helpers"
|
||||
"github.com/armosec/kubescape/v2/core/core"
|
||||
utilsapisv1 "github.com/armosec/opa-utils/httpserver/apis/v1"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// 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
|
||||
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()
|
||||
|
||||
scanID := uuid.NewString()
|
||||
handler.state.setID(scanID)
|
||||
handler.state.setBusy(scanID)
|
||||
defer handler.state.setNotBusy(scanID)
|
||||
|
||||
resultsFile := filepath.Join(OutputDir, scanID)
|
||||
scanInfo := getPrometheusDefaultScanCommand(scanID, resultsFile)
|
||||
|
||||
// trigger scanning
|
||||
logger.L().Info(handler.state.getID(), helpers.String("action", "triggering scan"), helpers.Time())
|
||||
ks := core.NewKubescape()
|
||||
results, err := ks.Scan(getPrometheusDefaultScanCommand(scanID, resultsFile))
|
||||
if err != nil {
|
||||
scanParams := &scanRequestParams{
|
||||
scanQueryParams: &ScanQueryParams{
|
||||
ReturnResults: true,
|
||||
KeepResults: false,
|
||||
},
|
||||
scanInfo: scanInfo,
|
||||
scanID: scanID,
|
||||
}
|
||||
|
||||
handler.scanResponseChan.set(scanID) // add scan to channel
|
||||
defer handler.scanResponseChan.delete(scanID)
|
||||
|
||||
// send to scan queue
|
||||
handler.scanRequestChan <- scanParams
|
||||
|
||||
// wait for scan to complete
|
||||
results := <-handler.scanResponseChan.get(scanID)
|
||||
defer removeResultsFile(scanID) // remove json format results file
|
||||
defer os.Remove(resultsFile) // remove prometheus format results file
|
||||
|
||||
// handle response
|
||||
if results.Type == utilsapisv1.ErrorScanResponseType {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte(fmt.Sprintf("failed to complete scan. reason: %s", err.Error())))
|
||||
w.Write(responseToBytes(results))
|
||||
return
|
||||
}
|
||||
results.HandleResults()
|
||||
logger.L().Info(handler.state.getID(), helpers.String("action", "done scanning"), helpers.Time())
|
||||
|
||||
// read prometheus format results file
|
||||
f, err := os.ReadFile(resultsFile)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte(fmt.Sprintf("failed read results from file. reason: %s", err.Error())))
|
||||
results.Type = utilsapisv1.ErrorScanResponseType
|
||||
results.Response = fmt.Sprintf("failed read results from file. reason: %s", err.Error())
|
||||
w.Write(responseToBytes(results))
|
||||
return
|
||||
}
|
||||
os.Remove(resultsFile)
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write(f)
|
||||
|
||||
110
httphandler/handlerequests/v1/requestparser.go
Normal file
110
httphandler/handlerequests/v1/requestparser.go
Normal file
@@ -0,0 +1,110 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"sync"
|
||||
|
||||
"github.com/armosec/kubescape/v2/core/cautils"
|
||||
"github.com/armosec/kubescape/v2/core/cautils/logger"
|
||||
"github.com/armosec/kubescape/v2/core/cautils/logger/helpers"
|
||||
utilsmetav1 "github.com/armosec/opa-utils/httpserver/meta/v1"
|
||||
"github.com/gorilla/schema"
|
||||
)
|
||||
|
||||
type scanResponseChan struct {
|
||||
scanResponseChan map[string]chan *utilsmetav1.Response
|
||||
mtx sync.RWMutex
|
||||
}
|
||||
|
||||
// get response object chan
|
||||
func (resChan *scanResponseChan) get(key string) chan *utilsmetav1.Response {
|
||||
resChan.mtx.RLock()
|
||||
defer resChan.mtx.RUnlock()
|
||||
return resChan.scanResponseChan[key]
|
||||
}
|
||||
|
||||
// set chan for response object
|
||||
func (resChan *scanResponseChan) set(key string) {
|
||||
resChan.mtx.Lock()
|
||||
defer resChan.mtx.Unlock()
|
||||
resChan.scanResponseChan[key] = make(chan *utilsmetav1.Response)
|
||||
}
|
||||
|
||||
// push response object to chan
|
||||
func (resChan *scanResponseChan) push(key string, resp *utilsmetav1.Response) {
|
||||
resChan.mtx.Lock()
|
||||
defer resChan.mtx.Unlock()
|
||||
if _, ok := resChan.scanResponseChan[key]; ok {
|
||||
resChan.scanResponseChan[key] <- resp
|
||||
}
|
||||
}
|
||||
|
||||
// delete channel
|
||||
func (resChan *scanResponseChan) delete(key string) {
|
||||
resChan.mtx.Lock()
|
||||
defer resChan.mtx.Unlock()
|
||||
delete(resChan.scanResponseChan, key)
|
||||
}
|
||||
func newScanResponseChan() *scanResponseChan {
|
||||
return &scanResponseChan{
|
||||
scanResponseChan: make(map[string]chan *utilsmetav1.Response),
|
||||
mtx: sync.RWMutex{},
|
||||
}
|
||||
}
|
||||
|
||||
type ScanQueryParams struct {
|
||||
ReturnResults bool `schema:"wait"` // wait for scanning to complete (synchronized request)
|
||||
KeepResults bool `schema:"keep"` // do not delete results after returning (relevant only for synchronized requests)
|
||||
}
|
||||
|
||||
type ResultsQueryParams struct {
|
||||
ScanID string `schema:"id"`
|
||||
KeepResults bool `schema:"keep"` // do not delete results after returning (default will delete results)
|
||||
AllResults bool `schema:"all"` // delete all results
|
||||
}
|
||||
|
||||
type StatusQueryParams struct {
|
||||
ScanID string `schema:"id"`
|
||||
}
|
||||
|
||||
// scanRequestParams params passed to channel
|
||||
type scanRequestParams struct {
|
||||
scanInfo *cautils.ScanInfo // request as received from api
|
||||
scanQueryParams *ScanQueryParams // request as received from api
|
||||
scanID string // generated scan ID
|
||||
}
|
||||
|
||||
func getScanParamsFromRequest(r *http.Request, scanID string) (*scanRequestParams, error) {
|
||||
defer r.Body.Close()
|
||||
|
||||
scanRequestParams := &scanRequestParams{}
|
||||
|
||||
scanQueryParams := &ScanQueryParams{}
|
||||
if err := schema.NewDecoder().Decode(scanQueryParams, r.URL.Query()); err != nil {
|
||||
return scanRequestParams, fmt.Errorf("failed to parse query params, reason: %s", err.Error())
|
||||
}
|
||||
|
||||
readBuffer, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
// handler.writeError(w, fmt.Errorf("failed to read request body, reason: %s", err.Error()), scanID)
|
||||
return scanRequestParams, fmt.Errorf("failed to read request body, reason: %s", err.Error())
|
||||
}
|
||||
|
||||
logger.L().Info("REST API received scan request", helpers.String("body", string(readBuffer)))
|
||||
|
||||
scanRequest := &utilsmetav1.PostScanRequest{}
|
||||
if err := json.Unmarshal(readBuffer, &scanRequest); err != nil {
|
||||
return scanRequestParams, fmt.Errorf("failed to parse request payload, reason: %s", err.Error())
|
||||
}
|
||||
|
||||
scanInfo := getScanCommand(scanRequest, scanID)
|
||||
|
||||
scanRequestParams.scanID = scanID
|
||||
scanRequestParams.scanQueryParams = scanQueryParams
|
||||
scanRequestParams.scanInfo = scanInfo
|
||||
|
||||
return scanRequestParams, nil
|
||||
}
|
||||
76
httphandler/handlerequests/v1/requestparser_test.go
Normal file
76
httphandler/handlerequests/v1/requestparser_test.go
Normal file
@@ -0,0 +1,76 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"testing"
|
||||
|
||||
utilsmetav1 "github.com/armosec/opa-utils/httpserver/meta/v1"
|
||||
"github.com/armosec/utils-go/boolutils"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestGetScanParamsFromRequest(t *testing.T) {
|
||||
{
|
||||
body := utilsmetav1.PostScanRequest{
|
||||
Submit: boolutils.BoolPointer(true),
|
||||
HostScanner: boolutils.BoolPointer(true),
|
||||
Account: "aaaaaaaaaa",
|
||||
}
|
||||
|
||||
jsonBytes, err := json.Marshal(body)
|
||||
assert.NoError(t, err)
|
||||
|
||||
u := url.URL{
|
||||
Scheme: "http",
|
||||
Host: "bla",
|
||||
Path: "bla",
|
||||
RawQuery: "wait=true&keep=true",
|
||||
}
|
||||
request, err := http.NewRequest(http.MethodPost, u.String(), bytes.NewReader(jsonBytes))
|
||||
assert.NoError(t, err)
|
||||
|
||||
scanID := "ccccccc"
|
||||
|
||||
req, err := getScanParamsFromRequest(request, scanID)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, scanID, req.scanID)
|
||||
assert.True(t, req.scanQueryParams.KeepResults)
|
||||
assert.True(t, req.scanQueryParams.ReturnResults)
|
||||
assert.True(t, req.scanInfo.HostSensorEnabled.GetBool())
|
||||
assert.True(t, req.scanInfo.Submit)
|
||||
assert.Equal(t, "aaaaaaaaaa", req.scanInfo.Account)
|
||||
}
|
||||
|
||||
{
|
||||
body := utilsmetav1.PostScanRequest{
|
||||
Submit: boolutils.BoolPointer(false),
|
||||
HostScanner: boolutils.BoolPointer(false),
|
||||
Account: "aaaaaaaaaa",
|
||||
}
|
||||
|
||||
jsonBytes, err := json.Marshal(body)
|
||||
assert.NoError(t, err)
|
||||
|
||||
u := url.URL{
|
||||
Scheme: "http",
|
||||
Host: "bla",
|
||||
Path: "bla",
|
||||
}
|
||||
request, err := http.NewRequest(http.MethodPost, u.String(), bytes.NewReader(jsonBytes))
|
||||
assert.NoError(t, err)
|
||||
|
||||
scanID := "ccccccc"
|
||||
|
||||
req, err := getScanParamsFromRequest(request, scanID)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, scanID, req.scanID)
|
||||
assert.False(t, req.scanQueryParams.KeepResults)
|
||||
assert.False(t, req.scanQueryParams.ReturnResults)
|
||||
assert.False(t, req.scanInfo.HostSensorEnabled.GetBool())
|
||||
assert.False(t, req.scanInfo.Submit)
|
||||
assert.Equal(t, "aaaaaaaaaa", req.scanInfo.Account)
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,8 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"sync"
|
||||
|
||||
utilsapisv1 "github.com/armosec/opa-utils/httpserver/apis/v1"
|
||||
utilsmetav1 "github.com/armosec/opa-utils/httpserver/meta/v1"
|
||||
@@ -19,35 +16,27 @@ import (
|
||||
var OutputDir = "./results"
|
||||
var FailedOutputDir = "./failed"
|
||||
|
||||
type ScanQueryParams struct {
|
||||
ReturnResults bool `schema:"wait"` // wait for scanning to complete (synchronized request)
|
||||
KeepResults bool `schema:"keep"` // do not delete results after returning (relevant only for synchronized requests)
|
||||
}
|
||||
|
||||
type ResultsQueryParams struct {
|
||||
ScanID string `schema:"id"`
|
||||
KeepResults bool `schema:"keep"` // do not delete results after returning (default will delete results)
|
||||
AllResults bool `schema:"all"` // delete all results
|
||||
}
|
||||
|
||||
type StatusQueryParams struct {
|
||||
ScanID string `schema:"id"`
|
||||
}
|
||||
|
||||
type HTTPHandler struct {
|
||||
state *serverState
|
||||
state *serverState
|
||||
scanResponseChan *scanResponseChan
|
||||
scanRequestChan chan *scanRequestParams
|
||||
}
|
||||
|
||||
func NewHTTPHandler() *HTTPHandler {
|
||||
return &HTTPHandler{
|
||||
state: newServerState(),
|
||||
handler := &HTTPHandler{
|
||||
state: newServerState(),
|
||||
scanRequestChan: make(chan *scanRequestParams),
|
||||
scanResponseChan: newScanResponseChan(),
|
||||
}
|
||||
go handler.executeScan()
|
||||
|
||||
return handler
|
||||
}
|
||||
|
||||
// ============================================== STATUS ========================================================
|
||||
// Status API
|
||||
func (handler *HTTPHandler) Status(w http.ResponseWriter, r *http.Request) {
|
||||
defer handler.recover(w)
|
||||
defer handler.recover(w, "")
|
||||
|
||||
if r.Method != http.MethodGet {
|
||||
w.WriteHeader(http.StatusMethodNotAllowed)
|
||||
@@ -59,120 +48,81 @@ func (handler *HTTPHandler) Status(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
statusQueryParams := &StatusQueryParams{}
|
||||
if err := schema.NewDecoder().Decode(statusQueryParams, r.URL.Query()); err != nil {
|
||||
handler.writeError(w, fmt.Errorf("failed to parse query params, reason: %s", err.Error()))
|
||||
handler.writeError(w, fmt.Errorf("failed to parse query params, reason: %s", err.Error()), "")
|
||||
return
|
||||
}
|
||||
|
||||
if !handler.state.isBusy() {
|
||||
if !handler.state.isBusy(statusQueryParams.ScanID) {
|
||||
response.Type = utilsapisv1.NotBusyScanResponseType
|
||||
w.Write(responseToBytes(&response))
|
||||
return
|
||||
}
|
||||
|
||||
currentScanID := handler.state.getID()
|
||||
if statusQueryParams.ScanID != "" && currentScanID != statusQueryParams.ScanID {
|
||||
response.Type = utilsapisv1.NotBusyScanResponseType
|
||||
w.Write(responseToBytes(&response))
|
||||
return
|
||||
if statusQueryParams.ScanID == "" {
|
||||
statusQueryParams.ScanID = handler.state.getLatestID()
|
||||
}
|
||||
|
||||
response.Response = currentScanID
|
||||
response.ID = currentScanID
|
||||
response.Response = statusQueryParams.ScanID
|
||||
response.ID = statusQueryParams.ScanID
|
||||
response.Type = utilsapisv1.BusyScanResponseType
|
||||
w.Write(responseToBytes(&response))
|
||||
}
|
||||
|
||||
// ============================================== SCAN ========================================================
|
||||
// Scan API - TODO: break down to functions
|
||||
// Scan API
|
||||
func (handler *HTTPHandler) Scan(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
defer handler.recover(w)
|
||||
// generate id
|
||||
scanID := uuid.NewString()
|
||||
|
||||
defer r.Body.Close()
|
||||
defer handler.recover(w, scanID)
|
||||
|
||||
if r.Method != http.MethodPost {
|
||||
w.WriteHeader(http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
response := utilsmetav1.Response{}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
scanQueryParams := &ScanQueryParams{}
|
||||
if err := schema.NewDecoder().Decode(scanQueryParams, r.URL.Query()); err != nil {
|
||||
handler.writeError(w, fmt.Errorf("failed to parse query params, reason: %s", err.Error()))
|
||||
return
|
||||
}
|
||||
if handler.state.isBusy() {
|
||||
// TODO - Add to queue
|
||||
w.WriteHeader(http.StatusOK)
|
||||
response.Response = handler.state.getID()
|
||||
response.ID = handler.state.getID()
|
||||
response.Type = utilsapisv1.IDScanResponseType
|
||||
w.Write(responseToBytes(&response))
|
||||
return
|
||||
}
|
||||
|
||||
handler.state.setBusy()
|
||||
|
||||
// generate id
|
||||
scanID := uuid.NewString()
|
||||
handler.state.setID(scanID)
|
||||
response.ID = scanID
|
||||
response.Type = utilsapisv1.IDScanResponseType
|
||||
|
||||
readBuffer, err := ioutil.ReadAll(r.Body)
|
||||
scanRequestParams, err := getScanParamsFromRequest(r, scanID)
|
||||
if err != nil {
|
||||
handler.writeError(w, fmt.Errorf("failed to read request body, reason: %s", err.Error()))
|
||||
handler.writeError(w, err, "")
|
||||
return
|
||||
}
|
||||
|
||||
logger.L().Info("REST API received scan request", helpers.String("body", string(readBuffer)))
|
||||
handler.state.setBusy(scanID)
|
||||
|
||||
scanRequest := utilsmetav1.PostScanRequest{}
|
||||
if err := json.Unmarshal(readBuffer, &scanRequest); err != nil {
|
||||
handler.writeError(w, fmt.Errorf("failed to parse request payload, reason: %s", err.Error()))
|
||||
return
|
||||
}
|
||||
response := &utilsmetav1.Response{}
|
||||
response.ID = scanID
|
||||
response.Type = utilsapisv1.BusyScanResponseType
|
||||
response.Response = fmt.Sprintf("scanning '%s' is in progress", scanID)
|
||||
|
||||
var wg sync.WaitGroup
|
||||
if scanQueryParams.ReturnResults {
|
||||
wg.Add(1)
|
||||
} else {
|
||||
wg.Add(0)
|
||||
}
|
||||
statusCode := http.StatusOK
|
||||
handler.scanResponseChan.set(scanID) // add channel
|
||||
defer handler.scanResponseChan.delete(scanID)
|
||||
|
||||
// you must use a goroutine since the executeScan function is not always listening to the channel
|
||||
go func() {
|
||||
// execute scan in the background
|
||||
// send to scanning handler
|
||||
handler.scanRequestChan <- scanRequestParams
|
||||
}()
|
||||
|
||||
logger.L().Info("scan triggered", helpers.String("ID", scanID))
|
||||
if scanRequestParams.scanQueryParams.ReturnResults {
|
||||
// wait for scan to complete
|
||||
response = <-handler.scanResponseChan.get(scanID)
|
||||
|
||||
results, err := scan(&scanRequest, scanID)
|
||||
if err != nil {
|
||||
logger.L().Error("scanning failed", helpers.String("ID", scanID), helpers.Error(err))
|
||||
if scanQueryParams.ReturnResults {
|
||||
response.Type = utilsapisv1.ErrorScanResponseType
|
||||
response.Response = err.Error()
|
||||
statusCode = http.StatusInternalServerError
|
||||
}
|
||||
} else {
|
||||
logger.L().Success("done scanning", helpers.String("ID", scanID))
|
||||
if scanQueryParams.ReturnResults {
|
||||
response.Type = utilsapisv1.ResultsV1ScanResponseType
|
||||
response.Response = results
|
||||
wg.Done()
|
||||
}
|
||||
}
|
||||
if scanQueryParams.ReturnResults && !scanQueryParams.KeepResults {
|
||||
if scanRequestParams.scanQueryParams.KeepResults {
|
||||
// delete results after returning
|
||||
logger.L().Debug("deleting results", helpers.String("ID", scanID))
|
||||
removeResultsFile(scanID)
|
||||
}
|
||||
handler.state.setNotBusy()
|
||||
}()
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
statusCode := http.StatusOK
|
||||
if response.Type == utilsapisv1.ErrorScanResponseType {
|
||||
statusCode = http.StatusInternalServerError
|
||||
}
|
||||
|
||||
w.WriteHeader(statusCode)
|
||||
w.Write(responseToBytes(&response))
|
||||
w.Write(responseToBytes(response))
|
||||
}
|
||||
|
||||
// ============================================== RESULTS ========================================================
|
||||
@@ -182,13 +132,13 @@ func (handler *HTTPHandler) Results(w http.ResponseWriter, r *http.Request) {
|
||||
response := utilsmetav1.Response{}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
defer handler.recover(w)
|
||||
defer handler.recover(w, "")
|
||||
|
||||
defer r.Body.Close()
|
||||
|
||||
resultsQueryParams := &ResultsQueryParams{}
|
||||
if err := schema.NewDecoder().Decode(resultsQueryParams, r.URL.Query()); err != nil {
|
||||
handler.writeError(w, fmt.Errorf("failed to parse query params, reason: %s", err.Error()))
|
||||
handler.writeError(w, fmt.Errorf("failed to parse query params, reason: %s", err.Error()), "")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -198,22 +148,22 @@ func (handler *HTTPHandler) Results(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
if resultsQueryParams.ScanID == "" { // if no scan found
|
||||
logger.L().Info("empty scan ID")
|
||||
w.WriteHeader(http.StatusBadRequest) // Should we return ok?
|
||||
response.Response = "latest scan not found. trigger again"
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
response.Response = "latest scan not found"
|
||||
response.Type = utilsapisv1.ErrorScanResponseType
|
||||
w.Write(responseToBytes(&response))
|
||||
return
|
||||
}
|
||||
response.ID = resultsQueryParams.ScanID
|
||||
|
||||
if handler.state.isBusy() { // if requested ID is still scanning
|
||||
if resultsQueryParams.ScanID == handler.state.getID() {
|
||||
logger.L().Info("scan in process", helpers.String("ID", resultsQueryParams.ScanID))
|
||||
w.WriteHeader(http.StatusOK)
|
||||
response.Response = "scanning in progress"
|
||||
w.Write(responseToBytes(&response))
|
||||
return
|
||||
}
|
||||
if handler.state.isBusy(resultsQueryParams.ScanID) { // if requested ID is still scanning
|
||||
logger.L().Info("scan in process", helpers.String("ID", resultsQueryParams.ScanID))
|
||||
w.WriteHeader(http.StatusOK)
|
||||
response.Type = utilsapisv1.BusyScanResponseType
|
||||
response.Response = fmt.Sprintf("scanning '%s' in progress", resultsQueryParams.ScanID)
|
||||
w.Write(responseToBytes(&response))
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
switch r.Method {
|
||||
@@ -259,15 +209,10 @@ func (handler *HTTPHandler) Ready(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
|
||||
func responseToBytes(res *utilsmetav1.Response) []byte {
|
||||
b, _ := json.Marshal(res)
|
||||
return b
|
||||
}
|
||||
|
||||
func (handler *HTTPHandler) recover(w http.ResponseWriter) {
|
||||
func (handler *HTTPHandler) recover(w http.ResponseWriter, scanID string) {
|
||||
response := utilsmetav1.Response{}
|
||||
if err := recover(); err != nil {
|
||||
handler.state.setNotBusy()
|
||||
handler.state.setNotBusy(scanID)
|
||||
logger.L().Error("recover", helpers.Error(fmt.Errorf("%v", err)))
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
response.Response = fmt.Sprintf("%v", err)
|
||||
@@ -276,11 +221,11 @@ func (handler *HTTPHandler) recover(w http.ResponseWriter) {
|
||||
}
|
||||
}
|
||||
|
||||
func (handler *HTTPHandler) writeError(w http.ResponseWriter, err error) {
|
||||
func (handler *HTTPHandler) writeError(w http.ResponseWriter, err error, scanID string) {
|
||||
response := utilsmetav1.Response{}
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
response.Response = err.Error()
|
||||
response.Type = utilsapisv1.ErrorScanResponseType
|
||||
w.Write(responseToBytes(&response))
|
||||
handler.state.setNotBusy()
|
||||
handler.state.setNotBusy(scanID)
|
||||
}
|
||||
|
||||
32
httphandler/handlerequests/v1/requestshandler_test.go
Normal file
32
httphandler/handlerequests/v1/requestshandler_test.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package v1
|
||||
|
||||
// ============================================== STATUS ========================================================
|
||||
// Status API
|
||||
// func TestStatus(t *testing.T) {
|
||||
|
||||
// {
|
||||
// httpHandler := NewHTTPHandler()
|
||||
|
||||
// u := url.URL{
|
||||
// Scheme: "http",
|
||||
// Host: "bla",
|
||||
// Path: "bla",
|
||||
// RawQuery: "wait=true&keep=true",
|
||||
// }
|
||||
// request, err := http.NewRequest(http.MethodPost, u.String(), nil)
|
||||
// httpHandler.Status(nil, request)
|
||||
|
||||
// assert.NoError(t, err)
|
||||
|
||||
// scanID := "ccccccc"
|
||||
|
||||
// req, err := getScanParamsFromRequest(request, scanID)
|
||||
// assert.NoError(t, err)
|
||||
// assert.Equal(t, scanID, req.scanID)
|
||||
// assert.True(t, req.scanQueryParams.KeepResults)
|
||||
// assert.True(t, req.scanQueryParams.ReturnResults)
|
||||
// assert.True(t, *req.scanRequest.HostScanner)
|
||||
// assert.True(t, *req.scanRequest.Submit)
|
||||
// assert.Equal(t, "aaaaaaaaaa", req.scanRequest.Account)
|
||||
// }
|
||||
// }
|
||||
@@ -9,14 +9,48 @@ import (
|
||||
|
||||
"github.com/armosec/kubescape/v2/core/cautils"
|
||||
"github.com/armosec/kubescape/v2/core/cautils/getter"
|
||||
"github.com/armosec/kubescape/v2/core/cautils/logger"
|
||||
"github.com/armosec/kubescape/v2/core/cautils/logger/helpers"
|
||||
"github.com/armosec/kubescape/v2/core/core"
|
||||
utilsapisv1 "github.com/armosec/opa-utils/httpserver/apis/v1"
|
||||
utilsmetav1 "github.com/armosec/opa-utils/httpserver/meta/v1"
|
||||
reporthandlingv2 "github.com/armosec/opa-utils/reporthandling/v2"
|
||||
"github.com/armosec/utils-go/boolutils"
|
||||
)
|
||||
|
||||
func scan(scanRequest *utilsmetav1.PostScanRequest, scanID string) (*reporthandlingv2.PostureReport, error) {
|
||||
scanInfo := getScanCommand(scanRequest, scanID)
|
||||
// executeScan execute the scan request passed in the channel
|
||||
func (handler *HTTPHandler) executeScan() {
|
||||
for {
|
||||
scanReq := <-handler.scanRequestChan
|
||||
|
||||
logger.L().Info("triggering scan", helpers.String("scanID", scanReq.scanID))
|
||||
|
||||
response := &utilsmetav1.Response{}
|
||||
|
||||
logger.L().Info("scan triggered", helpers.String("ID", scanReq.scanID))
|
||||
results, err := scan(scanReq.scanInfo, scanReq.scanID)
|
||||
if err != nil {
|
||||
logger.L().Error("scanning failed", helpers.String("ID", scanReq.scanID), helpers.Error(err))
|
||||
if scanReq.scanQueryParams.ReturnResults {
|
||||
response.Type = utilsapisv1.ErrorScanResponseType
|
||||
response.Response = err.Error()
|
||||
}
|
||||
} else {
|
||||
logger.L().Success("done scanning", helpers.String("ID", scanReq.scanID))
|
||||
if scanReq.scanQueryParams.ReturnResults {
|
||||
response.Type = utilsapisv1.ResultsV1ScanResponseType
|
||||
response.Response = results
|
||||
}
|
||||
}
|
||||
|
||||
handler.state.setNotBusy(scanReq.scanID)
|
||||
|
||||
// return results
|
||||
handler.scanResponseChan.push(scanReq.scanID, response)
|
||||
|
||||
}
|
||||
}
|
||||
func scan(scanInfo *cautils.ScanInfo, scanID string) (*reporthandlingv2.PostureReport, error) {
|
||||
|
||||
ks := core.NewKubescape()
|
||||
result, err := ks.Scan(scanInfo)
|
||||
@@ -136,7 +170,7 @@ func writeScanErrorToFile(err error, scanID string) error {
|
||||
if e := os.MkdirAll(filepath.Dir(FailedOutputDir), os.ModePerm); e != nil {
|
||||
return fmt.Errorf("failed to scan. reason: '%s'. failed to save error in file - failed to create directory. reason: %s", err.Error(), e.Error())
|
||||
}
|
||||
f, e := os.Create(filepath.Join(FailedOutputDir, scanID))
|
||||
f, e := os.Create(filepath.Join(filepath.Dir(FailedOutputDir), scanID))
|
||||
if e != nil {
|
||||
return fmt.Errorf("failed to scan. reason: '%s'. failed to save error in file - failed to open file for writing. reason: %s", err.Error(), e.Error())
|
||||
}
|
||||
@@ -147,3 +181,9 @@ func writeScanErrorToFile(err error, scanID string) error {
|
||||
}
|
||||
return fmt.Errorf("failed to scan. reason: '%s'", err.Error())
|
||||
}
|
||||
|
||||
// responseToBytes convert response object to bytes
|
||||
func responseToBytes(res *utilsmetav1.Response) []byte {
|
||||
b, _ := json.Marshal(res)
|
||||
return b
|
||||
}
|
||||
|
||||
@@ -3,44 +3,32 @@ package v1
|
||||
import "sync"
|
||||
|
||||
type serverState struct {
|
||||
// response string
|
||||
busy bool
|
||||
id string
|
||||
statusID map[string]bool
|
||||
latestID string
|
||||
mtx sync.RWMutex
|
||||
}
|
||||
|
||||
func (s *serverState) isBusy() bool {
|
||||
// isBusy is server busy with ID, if id is empty will check for latest ID
|
||||
func (s *serverState) isBusy(id string) bool {
|
||||
s.mtx.RLock()
|
||||
busy := s.busy
|
||||
if id == "" {
|
||||
id = s.latestID
|
||||
}
|
||||
busy := s.statusID[id]
|
||||
s.mtx.RUnlock()
|
||||
return busy
|
||||
}
|
||||
|
||||
func (s *serverState) setBusy() {
|
||||
func (s *serverState) setBusy(id string) {
|
||||
s.mtx.Lock()
|
||||
s.busy = true
|
||||
s.statusID[id] = true
|
||||
s.latestID = id
|
||||
s.mtx.Unlock()
|
||||
}
|
||||
|
||||
func (s *serverState) setNotBusy() {
|
||||
func (s *serverState) setNotBusy(id string) {
|
||||
s.mtx.Lock()
|
||||
s.busy = false
|
||||
s.latestID = s.id
|
||||
s.id = ""
|
||||
s.mtx.Unlock()
|
||||
}
|
||||
|
||||
func (s *serverState) getID() string {
|
||||
s.mtx.RLock()
|
||||
id := s.id
|
||||
s.mtx.RUnlock()
|
||||
return id
|
||||
}
|
||||
|
||||
func (s *serverState) setID(id string) {
|
||||
s.mtx.Lock()
|
||||
s.id = id
|
||||
delete(s.statusID, id)
|
||||
s.mtx.Unlock()
|
||||
}
|
||||
|
||||
@@ -51,9 +39,16 @@ func (s *serverState) getLatestID() string {
|
||||
return id
|
||||
}
|
||||
|
||||
func (s *serverState) len() int {
|
||||
s.mtx.RLock()
|
||||
l := len(s.statusID)
|
||||
s.mtx.RUnlock()
|
||||
return l
|
||||
}
|
||||
|
||||
func newServerState() *serverState {
|
||||
return &serverState{
|
||||
busy: false,
|
||||
mtx: sync.RWMutex{},
|
||||
statusID: make(map[string]bool),
|
||||
mtx: sync.RWMutex{},
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user