Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Service Extension Callout (Envoy external processing) #2895

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 99 additions & 0 deletions .github/workflows/service-extensions-publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
name: Publish Service Extensions Callout images packages

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 Code Vulnerability

No explicit permissions set for at the workflow level (...read more)

Check the permissions granted to jobs

Datadog’s GitHub organization defines default permissions for the GITHUB_TOKEN to be restricted (contents:read, metadata:read and packages:read).

Your repository may require different setup, but please consider defining permissions for each job following the least privilege principle to restrict the impact of a possible compromission.

You can find the list of all possible permissions in Workflow syntax for GitHub Actions - GitHub Docs. Please note they can be defined at the job or the workflow level.

View in Datadog  Leave us feedback  Documentation


on:
push:
branches:
- 'flavien/service-extensions'
release:
types:
- published
workflow_dispatch:
inputs:
tag_name:
description: 'Docker image tag to use for the package'
required: true
default: 'dev'
commit_sha:
description: 'Commit SHA to checkout'
required: true

permissions:
contents: read
packages: write

jobs:
publish-service-extensions:
runs-on: ubuntu-latest
steps:

- name: Get tag name
id: get_tag_name
run: |
if [ "${{ github.event_name }}" = "release" ]; then
echo "::set-output name=tag::${{ github.event.release.tag_name }}"
echo "Here1: tag=${{ github.event.release.tag_name }}"
else
if [ -z "${{ github.event.inputs.tag_name }}" ]; then
echo "::set-output name=tag::dev"
echo "Here2: tag=dev"
else
echo "::set-output name=tag::${{ github.event.inputs.tag_name }}"
echo "Here3: tag=${{ github.event.inputs.tag_name }}"
fi
fi
echo "Finally: ${{ steps.get_tag_name.outputs.tag }}"

- name: Checkout
uses: actions/checkout@v4

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 Code Vulnerability

Workflow depends on a GitHub actions pinned by tag (...read more)

Pin third party actions by hash, or at least by tag for trusted sources

When using a third party action, one needs to provide its GitHub path (owner/project) and can eventually pin it to a git ref (a branch name, a git tag, or a commit hash).

No pinned git ref means the action will use the latest commit of the default branch each time it runs, eventually running newer versions of the code that were not audited by Datadog. Specifying a git tag is better, but since they are not immutable, using a full length hash is recommended to make sure the action content is actually frozen to some reviewed state.

Be careful however, as even pinning an action by hash can be circumvented by attackers still. For instance, if an action relies on a Docker image which is itself not pinned to a digest, it becomes possible to alter its behaviour through the Docker image without actually changing its hash. You can learn more about this kind of attacks in Unpinnable Actions: How Malicious Code Can Sneak into Your GitHub Actions Workflows. Pinning actions by hash is still a good first line of defense against supply chain attacks.

Additionally, pinning by hash or tag means the action won’t benefit from newer version updates if any, including eventual security patches. Make sure to regularly check if newer versions for an action you use are available. For actions coming from a very trustworthy source, it can make sense to use a laxer pinning policy to benefit from updates as soon as possible.

View in Datadog  Leave us feedback  Documentation

if: github.event_name == 'release'
with:
ref: ${{ steps.get_tag_name.outputs.tag }}

- name: Checkout
uses: actions/checkout@v4
if: github.event_name != 'release'
with:
ref: ${{ github.event.inputs.commit_sha || github.sha }}

- name: Set up Go 1.22
uses: actions/setup-go@v5

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 Code Vulnerability

Workflow depends on a GitHub actions pinned by tag (...read more)

Pin third party actions by hash, or at least by tag for trusted sources

When using a third party action, one needs to provide its GitHub path (owner/project) and can eventually pin it to a git ref (a branch name, a git tag, or a commit hash).

No pinned git ref means the action will use the latest commit of the default branch each time it runs, eventually running newer versions of the code that were not audited by Datadog. Specifying a git tag is better, but since they are not immutable, using a full length hash is recommended to make sure the action content is actually frozen to some reviewed state.

Be careful however, as even pinning an action by hash can be circumvented by attackers still. For instance, if an action relies on a Docker image which is itself not pinned to a digest, it becomes possible to alter its behaviour through the Docker image without actually changing its hash. You can learn more about this kind of attacks in Unpinnable Actions: How Malicious Code Can Sneak into Your GitHub Actions Workflows. Pinning actions by hash is still a good first line of defense against supply chain attacks.

Additionally, pinning by hash or tag means the action won’t benefit from newer version updates if any, including eventual security patches. Make sure to regularly check if newer versions for an action you use are available. For actions coming from a very trustworthy source, it can make sense to use a laxer pinning policy to benefit from updates as soon as possible.

View in Datadog  Leave us feedback  Documentation

with:
go-version: 1.22
id: go

- name: Set up QEMU
uses: docker/setup-qemu-action@v3

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 Code Vulnerability

Workflow depends on a GitHub actions pinned by tag (...read more)

Pin third party actions by hash, or at least by tag for trusted sources

When using a third party action, one needs to provide its GitHub path (owner/project) and can eventually pin it to a git ref (a branch name, a git tag, or a commit hash).

No pinned git ref means the action will use the latest commit of the default branch each time it runs, eventually running newer versions of the code that were not audited by Datadog. Specifying a git tag is better, but since they are not immutable, using a full length hash is recommended to make sure the action content is actually frozen to some reviewed state.

Be careful however, as even pinning an action by hash can be circumvented by attackers still. For instance, if an action relies on a Docker image which is itself not pinned to a digest, it becomes possible to alter its behaviour through the Docker image without actually changing its hash. You can learn more about this kind of attacks in Unpinnable Actions: How Malicious Code Can Sneak into Your GitHub Actions Workflows. Pinning actions by hash is still a good first line of defense against supply chain attacks.

Additionally, pinning by hash or tag means the action won’t benefit from newer version updates if any, including eventual security patches. Make sure to regularly check if newer versions for an action you use are available. For actions coming from a very trustworthy source, it can make sense to use a laxer pinning policy to benefit from updates as soon as possible.

View in Datadog  Leave us feedback  Documentation


- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 Code Vulnerability

Workflow depends on a GitHub actions pinned by tag (...read more)

Pin third party actions by hash, or at least by tag for trusted sources

When using a third party action, one needs to provide its GitHub path (owner/project) and can eventually pin it to a git ref (a branch name, a git tag, or a commit hash).

No pinned git ref means the action will use the latest commit of the default branch each time it runs, eventually running newer versions of the code that were not audited by Datadog. Specifying a git tag is better, but since they are not immutable, using a full length hash is recommended to make sure the action content is actually frozen to some reviewed state.

Be careful however, as even pinning an action by hash can be circumvented by attackers still. For instance, if an action relies on a Docker image which is itself not pinned to a digest, it becomes possible to alter its behaviour through the Docker image without actually changing its hash. You can learn more about this kind of attacks in Unpinnable Actions: How Malicious Code Can Sneak into Your GitHub Actions Workflows. Pinning actions by hash is still a good first line of defense against supply chain attacks.

Additionally, pinning by hash or tag means the action won’t benefit from newer version updates if any, including eventual security patches. Make sure to regularly check if newer versions for an action you use are available. For actions coming from a very trustworthy source, it can make sense to use a laxer pinning policy to benefit from updates as soon as possible.

View in Datadog  Leave us feedback  Documentation


- name: Login to Docker
shell: bash
run: docker login -u publisher -p ${{ secrets.GITHUB_TOKEN }} ghcr.io

- name: Build and push [dev]
id: build-dev
if: github.event_name != 'release'
uses: docker/build-push-action@v6

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 Code Vulnerability

Workflow depends on a GitHub actions pinned by tag (...read more)

Pin third party actions by hash, or at least by tag for trusted sources

When using a third party action, one needs to provide its GitHub path (owner/project) and can eventually pin it to a git ref (a branch name, a git tag, or a commit hash).

No pinned git ref means the action will use the latest commit of the default branch each time it runs, eventually running newer versions of the code that were not audited by Datadog. Specifying a git tag is better, but since they are not immutable, using a full length hash is recommended to make sure the action content is actually frozen to some reviewed state.

Be careful however, as even pinning an action by hash can be circumvented by attackers still. For instance, if an action relies on a Docker image which is itself not pinned to a digest, it becomes possible to alter its behaviour through the Docker image without actually changing its hash. You can learn more about this kind of attacks in Unpinnable Actions: How Malicious Code Can Sneak into Your GitHub Actions Workflows. Pinning actions by hash is still a good first line of defense against supply chain attacks.

Additionally, pinning by hash or tag means the action won’t benefit from newer version updates if any, including eventual security patches. Make sure to regularly check if newer versions for an action you use are available. For actions coming from a very trustworthy source, it can make sense to use a laxer pinning policy to benefit from updates as soon as possible.

View in Datadog  Leave us feedback  Documentation

with:
context: .
file: ./contrib/envoyproxy/envoy/cmd/serviceextensions/Dockerfile
platforms: linux/amd64,linux/arm64
push: true
tags: | # Use the commit SHA from the manual trigger or default to the SHA from the push event
ghcr.io/datadog/dd-trace-go/service-extensions-callout:${{ steps.get_tag_name.outputs.tag }}
ghcr.io/datadog/dd-trace-go/service-extensions-callout:${{ github.event.inputs.commit_sha || github.sha }}

- name: Build and push [release]
id: build-release
if: github.event_name == 'release'
uses: docker/build-push-action@v6

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 Code Vulnerability

Workflow depends on a GitHub actions pinned by tag (...read more)

View in Datadog  Leave us feedback  Documentation

with:
context: .
file: ./contrib/envoyproxy/envoy/cmd/serviceextensions/Dockerfile
platforms: linux/amd64,linux/arm64
push: true
tags: |
ghcr.io/datadog/dd-trace-go/service-extensions-callout:latest
ghcr.io/datadog/dd-trace-go/service-extensions-callout:${{ steps.get_tag_name.outputs.tag }}
ghcr.io/datadog/dd-trace-go/service-extensions-callout:${{ github.sha }}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
serviceextensions
20 changes: 20 additions & 0 deletions contrib/envoyproxy/envoy/cmd/serviceextensions/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Build stage
FROM golang:1.22-alpine AS builder
ENV CGO_ENABLED=1
WORKDIR /app
COPY . .
RUN apk add --no-cache --update git build-base
RUN go build -o ./contrib/envoyproxy/envoy/cmd/serviceextensions/serviceextensions ./contrib/envoyproxy/envoy/cmd/serviceextensions

# Runtime stage
FROM alpine:3.20.3
RUN apk --no-cache add ca-certificates tzdata libc6-compat libgcc libstdc++
WORKDIR /app
COPY --from=builder /app/contrib/envoyproxy/envoy/cmd/serviceextensions/serviceextensions /app/serviceextensions
COPY ./contrib/envoyproxy/envoy/cmd/serviceextensions/localhost.crt /app/localhost.crt
COPY ./contrib/envoyproxy/envoy/cmd/serviceextensions/localhost.key /app/localhost.key

EXPOSE 80
EXPOSE 443

CMD ["./serviceextensions"]
19 changes: 19 additions & 0 deletions contrib/envoyproxy/envoy/cmd/serviceextensions/localhost.crt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
-----BEGIN CERTIFICATE-----
MIIDFjCCAf4CCQCzrLIhrWa55zANBgkqhkiG9w0BAQsFADBCMQswCQYDVQQGEwJV
UzETMBEGA1UECAwKQ2FsaWZvcm5pYTEPMA0GA1UECgwGR29vZ2xlMQ0wCwYDVQQL
DARnUlBDMCAXDTE5MDYyNDIyMjIzM1oYDzIxMTkwNTMxMjIyMjMzWjBWMQswCQYD
VQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEPMA0GA1UECgwGR29vZ2xlMQ0w
CwYDVQQLDARnUlBDMRIwEAYDVQQDDAlsb2NhbGhvc3QwggEiMA0GCSqGSIb3DQEB
AQUAA4IBDwAwggEKAoIBAQCtCW0TjugnIUu8BEVIYvdMP+/2GENQDjZhZ8eKR5C6
toDGbgjsDtt/GxISAg4cg70fIvy0XolnGPZodvfHDM4lJ7yHBOdZD8TXQoE6okR7
HZuLUJ20M0pXgWqtRewKRUjuYsSDXBnzLiZw1dcv9nGpo+Bqa8NonpiGRRpEkshF
D6T9KU9Ts/x+wMQBIra2Gj0UMh79jPhUuxcYAQA0JQGivnOtdwuPiumpnUT8j8h6
tWg5l01EsCZWJecCF85KnGpJEVYPyPqBqGsy0nGS9plGotOWF87+jyUQt+KD63xA
aBmTro86mKDDKEK4JvzjVeMGz2UbVcLPiiZnErTFaiXJAgMBAAEwDQYJKoZIhvcN
AQELBQADggEBAKsDgOPCWp5WCy17vJbRlgfgk05sVNIHZtzrmdswjBmvSg8MUpep
XqcPNUpsljAXsf9UM5IFEMRdilUsFGWvHjBEtNAW8WUK9UV18WRuU//0w1Mp5HAN
xUEKb4BoyZr65vlCnTR+AR5c9FfPvLibhr5qHs2RA8Y3GyLOcGqBWed87jhdQLCc
P1bxB+96le5JeXq0tw215lxonI2/3ZYVK4/ok9gwXrQoWm8YieJqitk/ZQ4S17/4
pynHtDfdxLn23EXeGx+UTxJGfpRmhEZdJ+MN7QGYoomzx5qS5XoYKxRNrDlirJpr
OqXIn8E1it+6d5gOZfuHawcNGhRLplE/pfA=
-----END CERTIFICATE-----
27 changes: 27 additions & 0 deletions contrib/envoyproxy/envoy/cmd/serviceextensions/localhost.key
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEogIBAAKCAQEArQltE47oJyFLvARFSGL3TD/v9hhDUA42YWfHikeQuraAxm4I
7A7bfxsSEgIOHIO9HyL8tF6JZxj2aHb3xwzOJSe8hwTnWQ/E10KBOqJEex2bi1Cd
tDNKV4FqrUXsCkVI7mLEg1wZ8y4mcNXXL/ZxqaPgamvDaJ6YhkUaRJLIRQ+k/SlP
U7P8fsDEASK2tho9FDIe/Yz4VLsXGAEANCUBor5zrXcLj4rpqZ1E/I/IerVoOZdN
RLAmViXnAhfOSpxqSRFWD8j6gahrMtJxkvaZRqLTlhfO/o8lELfig+t8QGgZk66P
OpigwyhCuCb841XjBs9lG1XCz4omZxK0xWolyQIDAQABAoIBADeq/Kh6JT3RfGf0
h8WN8TlaqHxnueAbcmtL0+oss+cdp7gu1jf7X6o4r0uT1a5ew40s2Fe+wj2kzkE1
ZOlouTlC22gkr7j7Vbxa7PBMG/Pvxoa/XL0IczZLsGImSJXVTG1E4SvRiZeulTdf
1GbdxhtpWV1jZe5Wd4Na3+SHxF5S7m3PrHiZlYdz1ND+8XZs1NlL9+ej72qSFul9
t/QjMWJ9pky/Wad5abnRLRyOsg+BsgnXbkUy2rD89ZxFMLda9pzXo3TPyAlBHonr
mkEsE4eRMWMpjBM79JbeyDdHn/cs/LjAZrzeDf7ugXr2CHQpKaM5O0PsNHezJII9
L5kCfzECgYEA4M/rz1UP1/BJoSqigUlSs0tPAg8a5UlkVsh6Osuq72IPNo8qg/Fw
oV/IiIS+q+obRcFj1Od3PGdTpCJwW5dzd2fXBQGmGdj0HucnCrs13RtBh91JiF5i
y/YYI9KfgOG2ZT9gG68T0gTs6jRrS3Qd83npqjrkJqMOd7s00MK9tUcCgYEAxQq7
T541oCYHSBRIIb0IrR25krZy9caxzCqPDwOcuuhaCqCiaq+ATvOWlSfgecm4eH0K
PCH0xlWxG0auPEwm4pA8+/WR/XJwscPZMuoht1EoKy1his4eKx/s7hHNeO6KOF0V
Y/zqIiuZnEwUoKbn7EqqNFSTT65PJKyGsICJFG8CgYAfaw9yl1myfQNdQb8aQGwN
YJ33FLNWje427qeeZe5KrDKiFloDvI9YDjHRWnPnRL1w/zj7fSm9yFb5HlMDieP6
MQnsyjEzdY2QcA+VwVoiv3dmDHgFVeOKy6bOAtaFxYWfGr9MvygO9t9BT/gawGyb
JVORlc9i0vDnrMMR1dV7awKBgBpTWLtGc/u1mPt0Wj7HtsUKV6TWY32a0l5owTxM
S0BdksogtBJ06DukJ9Y9wawD23WdnyRxlPZ6tHLkeprrwbY7dypioOKvy4a0l+xJ
g7+uRCOgqIuXBkjUtx8HmeAyXp0xMo5tWArAsIFFWOwt4IadYygitJvMuh44PraO
NcJZAoGADEiV0dheXUCVr8DrtSom8DQMj92/G/FIYjXL8OUhh0+F+YlYP0+F8PEU
yYIWEqL/S5tVKYshimUXQa537JcRKsTVJBG/ZKD2kuqgOc72zQy3oplimXeJDCXY
h2eAQ0u8GN6tN9C4t8Kp4a3y6FGsxgu+UTxdnL3YQ+yHAVhtCzo=
-----END RSA PRIVATE KEY-----
128 changes: 128 additions & 0 deletions contrib/envoyproxy/envoy/cmd/serviceextensions/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
package main

import (
"crypto/tls"
"gopkg.in/DataDog/dd-trace-go.v1/contrib/envoyproxy/envoy"
"gopkg.in/DataDog/dd-trace-go.v1/internal/log"
"gopkg.in/DataDog/dd-trace-go.v1/internal/version"
"net"
"net/http"
"os"

extproc "github.com/envoyproxy/go-control-plane/envoy/service/ext_proc/v3"
"github.com/gorilla/mux"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/reflection"
"gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer"
)

// AppsecCalloutExtensionService defines the struct that follows the ExternalProcessorServer interface.
type AppsecCalloutExtensionService struct {
extproc.ExternalProcessorServer
}

type serviceExtensionConfig struct {
extensionPort string
extensionHost string
healthcheckPort string
}

func loadConfig() serviceExtensionConfig {
extensionPort := os.Getenv("DD_SERVICE_EXTENSION_PORT")
if extensionPort == "" {
extensionPort = "443"
}

extensionHost := os.Getenv("DD_SERVICE_EXTENSION_HOST")
if extensionHost == "" {
extensionHost = "0.0.0.0"
}

healthcheckPort := os.Getenv("DD_SERVICE_EXTENSION_HEALTHCHECK_PORT")
if healthcheckPort == "" {
healthcheckPort = "80"
}

return serviceExtensionConfig{
extensionPort: extensionPort,
extensionHost: extensionHost,
healthcheckPort: healthcheckPort,
}
}

func main() {
var extensionService AppsecCalloutExtensionService

// Force set ASM as enabled only if the environment variable is not set
// Note: If the environment variable is set to false, it should be disabled
if os.Getenv("DD_APPSEC_ENABLED") == "" {
if err := os.Setenv("DD_APPSEC_ENABLED", "1"); err != nil {
log.Error("service_extension: failed to set DD_APPSEC_ENABLED environment variable: %v\n", err)
}
}

// TODO: Enable ASM standalone mode when it is developed (should be done for Q4 2024)

// Set the DD_VERSION to the current tracer version if not set
if os.Getenv("DD_VERSION") == "" {
if err := os.Setenv("DD_VERSION", version.Tag); err != nil {
log.Error("service_extension: failed to set DD_VERSION environment variable: %v\n", err)
}
}

config := loadConfig()

tracer.Start()

go StartGPRCSsl(&extensionService, config)
log.Info("service_extension: callout gRPC server started on %s:%s\n", config.extensionHost, config.extensionPort)

go startHealthCheck(config)
log.Info("service_extension: health check server started on %s:%s\n", config.extensionHost, config.healthcheckPort)

select {}
}

func startHealthCheck(config serviceExtensionConfig) {
muxServer := mux.NewRouter()
muxServer.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
_, err := w.Write([]byte(`{"status": "ok", "library": {"language": "golang", "version": "` + version.Tag + `"}}`))
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}

w.WriteHeader(http.StatusOK)
})

server := &http.Server{
Addr: config.extensionHost + ":" + config.healthcheckPort,
Handler: muxServer,
}

println(server.ListenAndServe())
}

func StartGPRCSsl(service extproc.ExternalProcessorServer, config serviceExtensionConfig) {
cert, err := tls.LoadX509KeyPair("localhost.crt", "localhost.key")
if err != nil {
log.Error("Failed to load key pair: %v\n", err)
}

lis, err := net.Listen("tcp", config.extensionHost+":"+config.extensionPort)
if err != nil {
log.Error("Failed to listen: %v\n", err)
}

si := envoy.StreamServerInterceptor()
creds := credentials.NewServerTLSFromCert(&cert)
grpcServer := grpc.NewServer(grpc.StreamInterceptor(si), grpc.Creds(creds))

extproc.RegisterExternalProcessorServer(grpcServer, service)
reflection.Register(grpcServer)
if err := grpcServer.Serve(lis); err != nil {
log.Error("service_extension: failed to serve gRPC: %v\n", err)
}
}
Loading
Loading