diff --git a/container-images/armo-datastructures.go b/container-images/armo-datastructures.go new file mode 100644 index 00000000..16e94f38 --- /dev/null +++ b/container-images/armo-datastructures.go @@ -0,0 +1,28 @@ +package containerimages + +import "time" + +type V2ListRequest struct { + // properties of the requested next page + // Use ValidatePageProperties to set PageSize field + PageSize *int `json:"pageSize,omitempty"` + // One can leave it empty for 0, then call ValidatePageProperties + PageNum *int `json:"pageNum,omitempty"` + // The time window of the list to return. Default: since - begining og the time, until - now. + Since *time.Time `json:"since,omitempty"` + Until *time.Time `json:"until,omitempty"` + // Which elements of the list to return, each field can hold multiple values separated by comma + // Example: ": {"severity": "High,Medium", "type": "61539,30303"} + // An empty map means "return the complete list" + InnerFilters []map[string]string `json:"innerFilters,omitempty"` + // How to order (sort) the list, field name + sort order (asc/desc), like https://www.w3schools.com/sql/sql_orderby.asp + // Example: "timestamp:asc,severity:desc" + OrderBy string `json:"orderBy,omitempty"` + // Cursor to the next page of former requset. Not supported yet + // Cursor cannot be used with another parameters of this struct + Cursor string `json:"cursor,omitempty"` + // FieldsList allow us to return only subset of the source document fields + // Don't expose FieldsList outside without well designed decision + FieldsList []string `json:"includeFields,omitempty"` + FieldsReverseKeywordMap map[string]string `json:"-,omitempty"` +} diff --git a/container-images/civ-armo-adaptor.go b/container-images/civ-armo-adaptor.go index df573233..a1f69016 100644 --- a/container-images/civ-armo-adaptor.go +++ b/container-images/civ-armo-adaptor.go @@ -6,6 +6,7 @@ import ( "fmt" "io/ioutil" "net/http" + "strings" ) type FeLoginData struct { @@ -24,14 +25,18 @@ type ArmoBeConfiguration struct { BackendUrl string `json:"backend"` AuthUrl string `json:"authUrl"` } +type ArmoSelectCustomer struct { + SelectedCustomerGuid string `json:"selectedCustomer"` +} type ArmoCivAdaptor struct { - registry string - accountId string - clientId string - accessKey string - feToken FeLoginResponse - armoUrls ArmoBeConfiguration + registry string + accountId string + clientId string + accessKey string + feToken FeLoginResponse + armoUrls ArmoBeConfiguration + authCookie string } func CreateArmoAdaptor(registry string, credentials map[string]string) (*ArmoCivAdaptor, error) { @@ -40,13 +45,13 @@ func CreateArmoAdaptor(registry string, credentials map[string]string) (*ArmoCiv var clientId string var ok bool if accountId, ok = credentials["accountId"]; !ok { - return nil, fmt.Errorf("Define accountId in credentials") + return nil, fmt.Errorf("define accountId in credentials") } if clientId, ok = credentials["clientId"]; !ok { - return nil, fmt.Errorf("Define clientId in credentials") + return nil, fmt.Errorf("define clientId in credentials") } if accessKey, ok = credentials["accessKey"]; !ok { - return nil, fmt.Errorf("Define accessKey in credentials") + return nil, fmt.Errorf("define accessKey in credentials") } armoCivAdaptor := ArmoCivAdaptor{registry: registry, accountId: accountId, clientId: clientId, accessKey: accessKey} err := armoCivAdaptor.initializeUrls() @@ -78,6 +83,50 @@ func (armoCivAdaptor *ArmoCivAdaptor) initializeUrls() error { } +func (armoCivAdaptor *ArmoCivAdaptor) getAuthCookie() (string, error) { + selectCustomer := ArmoSelectCustomer{SelectedCustomerGuid: armoCivAdaptor.accountId} + requestBody, _ := json.Marshal(selectCustomer) + requestUrl := fmt.Sprintf("%s/api/v1/openid_customers", armoCivAdaptor.armoUrls.BackendUrl) + client := &http.Client{} + httpRequest, err := http.NewRequest(http.MethodPost, requestUrl, bytes.NewBuffer(requestBody)) + if err != nil { + return "", err + } + httpRequest.Header.Set("Content-Type", "application/json") + httpRequest.Header.Set("Authorization", fmt.Sprintf("Bearer %s", armoCivAdaptor.feToken.Token)) + /*fmt.Println(requestUrl) + fmt.Println(httpRequest.Header.Get("Content-Type")) + fmt.Println(httpRequest.Header.Get("Authorization")) + fmt.Println(string(requestBody))*/ + httpResponse, err := client.Do(httpRequest) + if err != nil { + return "", err + } + defer httpResponse.Body.Close() + if httpResponse.StatusCode != http.StatusOK { + return "", fmt.Errorf("error getting cookie at %s: status %d", requestUrl, httpResponse.StatusCode) + } + + cookies := httpResponse.Header.Get("set-cookie") + if len(cookies) == 0 { + return "", fmt.Errorf("no cookie field in response from %s", requestUrl) + } + + authCookie := "" + for _, cookie := range strings.Split(cookies, ";") { + kv := strings.Split(cookie, "=") + if kv[0] == "auth" { + authCookie = kv[1] + } + } + + if len(authCookie) == 0 { + return "", fmt.Errorf("no auth cookie field in response from %s", requestUrl) + } + + return authCookie, nil +} + func (armoCivAdaptor *ArmoCivAdaptor) Login() error { feLoginData := FeLoginData{ClientId: armoCivAdaptor.clientId, Secret: armoCivAdaptor.accessKey} body, _ := json.Marshal(feLoginData) @@ -89,27 +138,69 @@ func (armoCivAdaptor *ArmoCivAdaptor) Login() error { } defer resp.Body.Close() - if resp.StatusCode == http.StatusOK { - body, err := ioutil.ReadAll(resp.Body) - if err != nil { - return err - } - var feLoginResponse FeLoginResponse - err = json.Unmarshal(body, &feLoginResponse) - armoCivAdaptor.feToken = feLoginResponse - //fmt.Printf("Token: %s\n", feLoginResponse.Token) - //fmt.Printf("Body: %s\n", string(body)) - if err != nil { - return err - } - } else { - body, err := ioutil.ReadAll(resp.Body) - if err != nil { - return fmt.Errorf("Error authenticating: %d", resp.StatusCode) - - } - return fmt.Errorf("Error authenticating: %d - %s", resp.StatusCode, string(body)) + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("error authenticating: %d", resp.StatusCode) } - fmt.Printf("Success!") + + responseBody, err := ioutil.ReadAll(resp.Body) + if err != nil { + return err + } + var feLoginResponse FeLoginResponse + err = json.Unmarshal(responseBody, &feLoginResponse) + armoCivAdaptor.feToken = feLoginResponse + if err != nil { + return err + } + /* Now we have JWT */ + + armoCivAdaptor.authCookie, err = armoCivAdaptor.getAuthCookie() + if err != nil { + return err + } + return nil } + +func (armoCivAdaptor *ArmoCivAdaptor) GetImageVulnerabilties(imageID *ContainerImageIdentifier) (*ContainerImageVulnerability, error) { + filter := []map[string]string{{"imageTag": imageID.Tag}} + pageSize := 100 + pageNumber := 1 + request := V2ListRequest{PageSize: &pageSize, PageNum: &pageNumber, InnerFilters: filter, OrderBy: "timestamp:desc"} + requestBody, _ := json.Marshal(request) + requestUrl := fmt.Sprintf("%s/api/v1/vulnerability/scanResultsSumSummary?customerGUID=%s", armoCivAdaptor.armoUrls.BackendUrl, armoCivAdaptor.accountId) + client := &http.Client{} + httpRequest, err := http.NewRequest("POST", requestUrl, bytes.NewBuffer(requestBody)) + httpRequest.Header.Set("Content-Type", "application/json") + httpRequest.Header.Set("Authorization", fmt.Sprintf("Bearer %s", armoCivAdaptor.feToken.Token)) + httpRequest.Header.Set("Cookie", fmt.Sprintf("auth=%s", armoCivAdaptor.authCookie)) + //fmt.Printf("**** token %s\n", armoCivAdaptor.feToken.Token) + resp, err := client.Do(httpRequest) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("error requests %s with %d", requestUrl, resp.StatusCode) + } + + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, err + } + + fmt.Println(string(body)) + + /*err = json.Unmarshal(body, &feLoginResponse) + if err != nil { + return nil, err + }*/ + + return nil, nil + // https://api-dev.armo.cloud/api/v1/vulnerability/scanResultsSumSummary?customerGUID=1e3a88bf-92ce-44f8-914e-cbe71830d566%22 +} + +func (armoCivAdaptor *ArmoCivAdaptor) GetImagesVulnerabilties(imageIDs []ContainerImageIdentifier) ([]ContainerImageVulnerability, error) { + return nil, nil +}