Set up integrations using webhooks

Learn how to create webhooks and enable custom integrations with Endor Labs application

Webhooks enable real-time communication between different systems or applications over the internet. They allow one application to send data to another application as soon as a specific event or a trigger occurs.

Use webhooks to integrate Endor Labs with applications such as Slack, Microsoft Teams or more, and instantly get notified about projects if your configured policies are violated.

When events are triggered, Endor Labs sends HTTP POST requests to URLs of your configured events, with all the information you need.

Configure a webhook integration

Set up a custom integration with Endor Labs webhooks.

  1. Sign in to Endor Labs and click Integrations from the sidebar.
  2. Navigate to Webhooks under Notifications and click Add.
  3. Click Add Notification Integration.
  4. Enter a name and description for this integration.
  5. Enter the URL endpoint for the webhooks.
  6. Enter the authentication method such as API Key, Basic, or None.
  7. Enter the details for the authentication method such as USERNAME, PASSWORD, or API KEY. Make sure the API Key has required permissions to post messages using webhook.
  8. If you want to ensure integrity, de-select Disable HMAC Integration Check and enter the HMAC Shared Key. The Hash-Based Message Authentication Code (HMAC) ensures the authenticity of a message using a cryptographic hash function and a secret key. The HMAC signature is passed as a header in the HTTP request.
  9. Click Add Notification Integration.

Endor Labs webhook payload

Endor Labs provides the following webhook payload, that you can customize for your needs.

Name Description
raw_notification Notification object
project_name Name of the project
ref_name Endor Labs reference name for this notification
project_url URL of the project
findings_map Complete list of findings indexed by the UUID
package_version_map Complete list of package version objects indexed by the package version UUID. The package version ID is referenced in the findings object
project_map Complete list of project objects indexed by the project ID. The project UUID is referenced in the findings or in the package version objects.

Example:

See the following example for the body of the HTTP POST request.

message NotificationData {
  Notification raw_notification = 1; // Actual Notification object

  google.protobuf.StringValue project_name = 2;

  google.protobuf.StringValue policy_name = 3;

  google.protobuf.StringValue ref_name = 4;   // Branch for which the scan is performed.

  google.protobuf.StringValue project_url = 5;

  map<string, internal.endor.ai.endor.v1.Finding> findings_map = 6;
  // A map of findings uuid to the actual Findings object.

  map<string, internal.endor.ai.endor.v1.PackageVersion> package_version_map = 7;
  // A map of package version uuids to the package versions object

  map<string, internal.endor.ai.endor.v1.Project> project_map = 8;
  // A map of the project uuid to the project object.

  enum NotificationType {
    NOTIFICATION_TYPE_UNSPECIFIED = 0;

    // Notification type when a notification is created.
    NOTIFICATION_TYPE_CREATE = 1;

    // Notification type when a notification is updated.
    NOTIFICATION_TYPE_UPDATE = 2;

    // Notification type when a noticiation is resolved.
    NOTIFICATION_TYPE_RESOLVED = 3;
  }

  NotificationType type = 9;
}

Use Endor Labs webhooks to integrate with Slack

If you use Slack as a collaborative tool, integrate Slack channels using webhooks in Endor Labs to publish notifications as messages in the respective channels.

  1. Create incoming webhooks in Slack
  2. Configure a webhook integration
  3. Create a webhook handler to post Slack notifications

Create incoming webhooks in Slack

Create an incoming webhook to your Slack channel to enable Endor Labs to post notifications in the channel. The webhook provides a unique URL which is used to integrate the channel in Endor Labs. To send messages into Slack using incoming webhooks, see Slack documentation.

If you have already created an incoming webhook in the channel, copy the unique URL and integrate the channel in Endor Labs.

Webhook handler example for Slack

Create a webhook handler or a cloud function to receive webhook requests generated by Endor Labs, authorize the request, and post messages to your Slack channel.

See the following code sample hosted as a cloud function or a webhook handler.

// 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"
	//"github.com/golang/protobuf/proto"
)


// Representation of Endor Labs' notification structure.
type Finding_Spec struct {
		ProjectUuid                 *wrapperspb.StringValue          `protobuf:"bytes,1,opt,name=project_uuid,json=projectUuid,proto3" json:"project_uuid,omitempty"`
		Summary                     *wrapperspb.StringValue          `protobuf:"bytes,8,opt,name=summary,proto3" json:"summary,omitempty"`
		Explanation                 *wrapperspb.StringValue          `protobuf:"bytes,16,opt,name=explanation,proto3" json:"explanation,omitempty"`
		LatestVersion               *wrapperspb.StringValue          `protobuf:"bytes,25,opt,name=latest_version,json=latestVersion,proto3" json:"latest_version,omitempty"`
		ProposedVersion             *wrapperspb.StringValue          `protobuf:"bytes,28,opt,name=proposed_version,json=proposedVersion,proto3" json:"proposed_version,omitempty"`
	}

type Finding struct {
		Uuid       *wrapperspb.StringValue `protobuf:"bytes,1,opt,name=uuid,proto3" json:"uuid,omitempty"`
		Spec       *Finding_Spec           `protobuf:"bytes,4,opt,name=spec,proto3" json:"spec,omitempty"`
}

// HelloWorld prints the JSON encoded "message" field in the body
// of the request or "Hello, World!" if there isn't one.
func HelloWorld(w http.ResponseWriter, r *http.Request) {
	var d struct {
		ProjectName       *wrapperspb.StringValue    `protobuf:"bytes,2,opt,name=project_name,json=projectName,proto3" json:"project_name,omitempty"`
		PolicyName        *wrapperspb.StringValue    `protobuf:"bytes,3,opt,name=policy_name,json=policyName,proto3" json:"policy_name,omitempty"`
		RefName           *wrapperspb.StringValue    `protobuf:"bytes,4,opt,name=ref_name,json=refName,proto3" json:"ref_name,omitempty"`
		ProjectUrl        *wrapperspb.StringValue    `protobuf:"bytes,5,opt,name=project_url,json=projectUrl,proto3" json:"project_url,omitempty"`
		FindingsMap       map[string]*Finding        `protobuf:"bytes,6,rep,name=findings_map,json=findingsMap,proto3" json:"findings_map,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
	}

	if err := json.NewDecoder(r.Body).Decode(&d); err != nil {
		switch err {
		case io.EOF:
			log.Printf("succcess")
			return
		default:
			log.Printf("json.NewDecoder: %v", err)
			http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
			return
		}
	}

	log.Printf("%s", d.ProjectName.Value)

	// 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]
			log.Printf("hmac sign %s", hmacSign)
		}
	}

	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("Hey there are findings in project %s which violates policy %s", d.ProjectName.Value, d.PolicyName.Value)
	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
	}
}