Skip to content

Commit

Permalink
⭐️ add scanner client
Browse files Browse the repository at this point in the history
  • Loading branch information
chris-rock committed Apr 12, 2022
1 parent 8d0f48e commit d2e8832
Show file tree
Hide file tree
Showing 3 changed files with 466 additions and 0 deletions.
152 changes: 152 additions & 0 deletions pkg/scanner/scanner.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
package scanner

import (
"bytes"
"context"
"encoding/json"
"io/ioutil"
"net"
"net/http"
"time"

"github.com/pkg/errors"
)

var (
DefaultHttpTimeout = 30 * time.Second
DefaultIdleConnTimeout = 30 * time.Second
DefaultTLSHandshakeTimeout = 10 * time.Second
)

func DefaultHttpClient() *http.Client {
tr := &http.Transport{
Proxy: http.ProxyFromEnvironment,
DialContext: (&net.Dialer{
Timeout: DefaultHttpTimeout,
KeepAlive: 30 * time.Second,
}).DialContext,
MaxIdleConns: 100,
IdleConnTimeout: DefaultIdleConnTimeout,
TLSHandshakeTimeout: DefaultTLSHandshakeTimeout,
ExpectContinueTimeout: 1 * time.Second,
}

httpClient := &http.Client{
Transport: tr,
Timeout: DefaultHttpTimeout,
}
return httpClient
}

type Scanner struct {
Endpoint string
Token string
httpclient http.Client
}

func (s *Scanner) request(ctx context.Context, url string, reqBodyBytes []byte) ([]byte, error) {
client := s.httpclient

header := make(http.Header)
header.Set("Accept", "application/json")
header.Set("Content-Type", "application/json")
header.Set("Authorization", "Bearer "+s.Token)

reader := bytes.NewReader(reqBodyBytes)
req, err := http.NewRequest("POST", url, reader)
if err != nil {
return nil, err
}
req = req.WithContext(ctx)
req.Header = header

// do http call
resp, err := client.Do(req)
if err != nil {
return nil, errors.Wrap(err, "failed to do request")
}

defer func() {
resp.Body.Close()
}()

return ioutil.ReadAll(resp.Body)
}

func (s *Scanner) HealthCheck(ctx context.Context, in *HealthCheckRequest) (*HealthCheckResponse, error) {
url := s.Endpoint + "/Health/Check"

reqBodyBytes, err := json.Marshal(in)
if err != nil {
return nil, errors.Wrap(err, "failed to marshal request")
}

respBodyBytes, err := s.request(ctx, url, reqBodyBytes)
if err != nil {
return nil, errors.Wrap(err, "failed to parse response")
}

out := HealthCheckResponse{}
if err = json.Unmarshal(respBodyBytes, &out); err != nil {
return nil, errors.Wrap(err, "failed to unmarshal proto response")
}

return &out, nil
}

func (s *Scanner) RunKubernetesManifest(ctx context.Context, in *KubernetesManifestJob) (*ScanResult, error) {
url := s.Endpoint + "/Scan/RunKubernetesManifest"

reqBodyBytes, err := json.Marshal(in)
if err != nil {
return nil, errors.Wrap(err, "failed to marshal request")
}

respBodyBytes, err := s.request(ctx, url, reqBodyBytes)
if err != nil {
return nil, errors.Wrap(err, "failed to parse response")
}

out := ScanResult{}
if err = json.Unmarshal(respBodyBytes, &out); err != nil {
return nil, errors.Wrap(err, "failed to unmarshal proto response")
}

return &out, nil
}

type KubernetesManifestJob struct {
Files []*File `json:"files,omitempty"`
}

type File struct {
Data []byte `json:"data,omitempty"`
}

type ScanResult struct {
WorstScore *Score `json:"worstScore,omitempty"`
Ok bool `json:"ok,omitempty"`
}

type Score struct {
QrId string `json:"qr_id,omitempty"`
Type uint32 `json:"type,omitempty"`
Value uint32 `json:"value,omitempty"`
Weight uint32 `json:"weight,omitempty"`
ScoreCompletion uint32 `json:"score_completion,omitempty"`
DataTotal uint32 `json:"data_total,omitempty"`
DataCompletion uint32 `json:"data_completion,omitempty"`
Message string `json:"message,omitempty"`
}

type HealthCheckRequest struct{}

type HealthCheckResponse struct {
Status string `json:"status,omitempty"`
// returns rfc 3339 timestamp
Time string `json:"time,omitempty"`
// returns the major api version
ApiVersion string `json:"apiVersion,omitempty"`
// returns the git commit checksum
Build string `json:"build,omitempty"`
}
93 changes: 93 additions & 0 deletions pkg/scanner/scanner_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package scanner

import (
"context"
"encoding/json"
"io/ioutil"
"net/http"
"net/http/httptest"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
"sigs.k8s.io/yaml"
)

func testServer() *httptest.Server {
mux := http.NewServeMux()
mux.HandleFunc("/Health/Check", func(w http.ResponseWriter, r *http.Request) {
result := &HealthCheckResponse{
Status: "SERVING",
}
data, err := json.Marshal(result)
if err != nil {
http.Error(w, err.Error(), 500)
return
}
w.Write(data)
})

mux.HandleFunc("/Scan/RunKubernetesManifest", func(w http.ResponseWriter, r *http.Request) {
result := &ScanResult{
Ok: true,
WorstScore: &Score{
Type: uint32(2),
Value: 100,
},
}
data, err := json.Marshal(result)
if err != nil {
http.Error(w, err.Error(), 500)
return
}
w.Write(data)
})
return httptest.NewServer(mux)
}

func TestScanner(t *testing.T) {
testserver := testServer()
url := testserver.URL
token := ""

// To test with a real client, just set
// url := "http://127.0.0.1:8990"
// token := "<token here>"

// do client request
s := &Scanner{
Endpoint: url,
Token: token,
}

// Run Health Check
healthResp, err := s.HealthCheck(context.Background(), &HealthCheckRequest{})
require.NoError(t, err)
assert.True(t, healthResp.Status == "SERVING")

// Run Manifest Scan
data, err := ioutil.ReadFile("./testdata/webhook-payload.json")
require.NoError(t, err)

request := admission.Request{}
err = yaml.Unmarshal(data, &request)
require.NoError(t, err)

k8sObjectData, err := yaml.Marshal(request.Object)
require.NoError(t, err)

result, err := s.RunKubernetesManifest(context.Background(), &KubernetesManifestJob{
Files: []*File{
{
Data: k8sObjectData,
},
},
})
require.NoError(t, err)
assert.NotNil(t, result)

// check if the scan passed
passed := result.WorstScore.Type == 2 && result.WorstScore.Value == 100
assert.True(t, passed)
}
Loading

0 comments on commit d2e8832

Please sign in to comment.