/
main.go
121 lines (105 loc) · 3.85 KB
/
main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
/*
Copyright 2022 Chainguard, Inc.
SPDX-License-Identifier: Apache-2.0
*/
package main
import (
"context"
"net/http"
"os"
"os/signal"
"strings"
"time"
"cloud.google.com/go/compute/metadata"
"cloud.google.com/go/pubsub"
cloudevents "github.com/cloudevents/sdk-go/v2"
cehttp "github.com/cloudevents/sdk-go/v2/protocol/http"
"github.com/coreos/go-oidc/v3/oidc"
"github.com/kelseyhightower/envconfig"
"golang.org/x/oauth2/google"
"google.golang.org/api/option"
"github.com/chainguard-dev/clog"
_ "github.com/chainguard-dev/clog/gcp/init"
"github.com/chainguard-dev/terraform-infra-common/pkg/httpmetrics"
mce "github.com/chainguard-dev/terraform-infra-common/pkg/httpmetrics/cloudevents"
"github.com/chainguard-dev/terraform-infra-common/pkg/profiler"
cgpubsub "github.com/chainguard-dev/terraform-infra-common/pkg/pubsub"
)
const (
retryDelay = 10 * time.Millisecond
maxRetry = 3
)
type envConfig struct {
Port int `envconfig:"PORT" default:"8080" required:"true"`
Topic string `envconfig:"PUBSUB_TOPIC" required:"true"`
}
func main() {
profiler.SetupProfiler()
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt)
defer cancel()
var env envConfig
if err := envconfig.Process("", &env); err != nil {
clog.Fatalf("failed to process env var: %s", err)
}
go httpmetrics.ServeMetrics()
defer httpmetrics.SetupTracer(ctx)()
c, err := mce.NewClientHTTP(cloudevents.WithPort(env.Port),
cehttp.WithRequestDataAtContextMiddleware() /* give request headers to the handler context */)
if err != nil {
clog.Fatalf("failed to create CE client, %v", err)
}
provider, err := oidc.NewProvider(ctx, "https://accounts.google.com")
if err != nil {
clog.Fatalf("failed to create OIDC provider, %v", err)
}
verifier := provider.Verifier(&oidc.Config{
// When on Cloud Run, this is checked by the platform.
SkipClientIDCheck: true,
})
projectID, err := metadata.ProjectID()
if err != nil {
clog.Fatalf("failed to get project ID, %v", err)
}
psc, err := pubsub.NewClient(ctx, projectID, option.WithTokenSource(google.ComputeTokenSource("")))
if err != nil {
clog.Fatalf("failed to create pubsub client, %v", err)
}
topic := psc.Topic(env.Topic)
defer topic.Stop()
if err := c.StartReceiver(cloudevents.ContextWithRetriesExponentialBackoff(ctx, retryDelay, maxRetry), func(ctx context.Context, event cloudevents.Event) error {
// We expect Chainguard webhooks to pass an Authorization header.
auth := strings.TrimPrefix(cehttp.RequestDataFromContext(ctx).Header.Get("Authorization"), "Bearer ")
if auth == "" {
return cloudevents.NewHTTPResult(http.StatusUnauthorized, "Unauthorized")
}
tok, err := verifier.Verify(ctx, auth)
if err != nil {
clog.FromContext(ctx).Errorf("failed to verify Authorization: %v", err)
return cloudevents.NewHTTPResult(http.StatusUnauthorized, err.Error())
}
var claims struct {
Email string `json:"email"`
EmailVerified bool `json:"email_verified"`
}
if err := tok.Claims(&claims); err != nil {
clog.FromContext(ctx).Errorf("failed to extract email claims: %v", err)
return cloudevents.NewHTTPResult(http.StatusUnauthorized, err.Error())
}
if !claims.EmailVerified {
clog.FromContext(ctx).Errorf("email claim is not verified: %s", claims.Email)
return cloudevents.NewHTTPResult(http.StatusUnauthorized, "Unverified email claim")
}
msg := cgpubsub.FromCloudEvent(ctx, event)
// Turn the email of the Google Service Account that sent the cloud
// event into an "actor" extension on the cloud event.
msg.Attributes["ce-actor"] = claims.Email
res := topic.Publish(ctx, msg)
if _, err := res.Get(ctx); err != nil {
clog.FromContext(ctx).Errorf("failed to forward event: %v\n%v", event, err)
return cloudevents.NewHTTPResult(http.StatusInternalServerError, err.Error())
}
return nil
}); err != nil {
clog.Fatalf("failed to start receiver, %v", err)
}
}