// Package p contains an HTTP Cloud Function.
package p
import (
"encoding/json"
"fmt"
"html"
"io"
"io/ioutil"
"bytes"
"log"
"net/http"
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"strings"
wrapperspb "google.golang.org/protobuf/types/known/wrapperspb"
)
// Struct representation of default webhook payload from Endor Lab's notification.
type WebhookMessage {
Data Payload `json:"data"`
}
type Payload struct {
Message string `json:"message"`
ProjectUrl string `json:"projectURL"`
Policy Policy `json:"policy"`
Findings []Finding `json:"findings"`
}
type Finding struct {
Uuid string `json:"uuid"`
Description string `json:"description"`
Severity string `json:"severity"`
Dependency string `json:"dependency,omitempty"`
Package string `json:"package,omitempty"`
RepositoryVersion string `json:"repositoryVersion,omitempty"`
FindingUrl string `json:"findingURL"`
}
type Policy struct {
Name string `json:"name"`
Url string `json:"url"`
}
// HelloWorld deserializes the default webhook payload from the notification object,
// formats it into a format that Slack supports and send the message to Slack via webhook.
func HelloWorld(w http.ResponseWriter, r *http.Request) {
var d WebhookMessage
if err := json.NewDecoder(r.Body).Decode(&d); err != nil {
switch err {
case io.EOF:
log.Printf("success")
return
default:
log.Printf("json.NewDecoder: %v", err)
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
return
}
}
// Perform the HMAC sign to make sure that the request is not tampered with.
hmacSign := ""
for headerName, headerValues := range r.Header {
if headerName == "X-Endor-Hmac-Signature" {
if headerValues[0] == "" {
http.Error(w, "hmac empty", http.StatusUnauthorized)
return
}
hmacSign = headerValues[0]
}
}
receivedMessage := d.Message
// Secret configured in Endor
secretKey := "Secret"
// Validate the HMAC
isValid := validateHMAC(receivedMessage, hmacSign, secretKey)
// Process the result
if isValid {
fmt.Fprint(w, html.EscapeString("success"))
} else {
http.Error(w, "unauthorized, something changed", http.StatusUnauthorized)
return
}
textToSlack := fmt.Sprintf("%s which violates policy %s", d.Data.Message, d.Data.Policy.Name)
sendMessageToSlack(textToSlack)
}
func validateHMAC(receivedMessage, receivedHMAC, secretKey string) bool {
// Create a new HMAC hasher using the SHA-256 hash function and the secret key
mac := hmac.New(sha256.New, []byte(secretKey))
// Write the received message to the HMAC hasher
mac.Write([]byte(receivedMessage))
// Calculate the HMAC value
expectedHMAC := mac.Sum(nil)
// Convert the expected HMAC to a hexadecimal string
expectedHMACString := hex.EncodeToString(expectedHMAC)
// Compare the expected HMAC with the received HMAC (ignoring case)
return strings.EqualFold(receivedHMAC, expectedHMACString)
}
func sendMessageToSlack(msg string) {
// Replace this url with the url hook from the Slack App
url := "https://slack.webhook"
payload := []byte(`{"text": "Hey there are findings in project https://github.com/endorlabs/python-deps.git which violates policy DemoNotification"}`)
req, err := http.NewRequest("POST", url, bytes.NewBuffer(payload))
if err != nil {
fmt.Println("Error creating request:", err)
return
}
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
fmt.Println("Error sending request:", err)
return
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Println("Error reading response body:", err)
return
}
}