├── .gitmodules
├── ca-key.yml
├── docs
├── README.md
├── authorizer.png
├── aws-diagram.png
├── geneve-packet-http2.png
├── sts-rickroll.png
├── upside-down.png
├── vendors-aws-blog.png
└── wireshark-demo.png
├── examples
├── account_id_emf
│ ├── account_id_emf.go
│ └── parse.go
├── cloudfront_functions
│ ├── cloudfront_functions.go
│ ├── cloudfront_functions_test.go
│ ├── rick.go
│ └── rick.js
├── flowdogshark
│ └── flowdogshark.go
├── geneve_headers
│ └── geneve_headers.go
├── lambda_acceptor
│ ├── cfn.yml
│ ├── enricher.go
│ ├── lambda
│ │ └── lambda.go
│ └── lambda_acceptor.go
├── sts_rickroll
│ └── sts_rickroll.go
├── upsidedown
│ └── upsidedown.go
└── webfirehose
│ ├── webfirehose.go
│ └── webfirehose.yml
├── go.mod
├── go.sum
├── gwlb
├── active_flow.go
├── context.go
├── default_handler.go
├── dial.go
├── fastpath.go
├── geneve_options.go
├── interceptors.go
├── mirror
│ └── mirrored_packet.go
├── serve.go
├── shark
│ ├── filter.go
│ ├── shark.go
│ ├── shark.pb.go
│ ├── shark.proto
│ └── shark_grpc.pb.go
├── udpconn_map.go
└── websocket.go
├── kmssigner
├── generate
│ └── generate.go
└── kmssigner.go
├── main.go
└── mytls
├── intermediate.go
└── tls_config.go
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "forks/gopacket"]
2 | path = forks/gopacket
3 | url = git@github.com:aidansteele/gopacket.git
4 |
--------------------------------------------------------------------------------
/ca-key.yml:
--------------------------------------------------------------------------------
1 | AWSTemplateFormatVersion: "2010-09-09"
2 |
3 | Resources:
4 | Alias:
5 | Type: AWS::KMS::Alias
6 | Properties:
7 | AliasName: alias/flowdog-ca
8 | TargetKeyId: !Ref Key
9 |
10 | Key:
11 | Type: AWS::KMS::Key
12 | Properties:
13 | Description: Flowdog Root CA
14 | KeySpec: ECC_NIST_P256
15 | KeyUsage: SIGN_VERIFY
16 | KeyPolicy:
17 | Version: "2012-10-17"
18 | Id: key-policy
19 | Statement:
20 | - Sid: AllowIAM
21 | Effect: Allow
22 | Action: kms:*
23 | Resource: "*"
24 | Principal:
25 | AWS: !Sub arn:aws:iam::${AWS::AccountId}:root
26 |
27 | Outputs:
28 | Alias:
29 | Value: !Ref Alias
30 | Key:
31 | Value: !GetAtt Key.Arn
32 |
--------------------------------------------------------------------------------
/docs/README.md:
--------------------------------------------------------------------------------
1 | [Twitter thread](https://twitter.com/__steele/status/1481227583274758146)
2 |
3 | # flowdog
4 |
5 | This is an application/framework for inspection and manipulation of network
6 | traffic in AWS VPCs. Packets routed to or from the Internet, between VPCs,
7 | between subnets can all be programmatically inspected or modified in great
8 | detail.
9 |
10 | This is achieved via AWS Gateway Load Balancers. GWLBs are a cloud-native
11 | alternative to NAT instances. They can auto-scale, they can be highly available
12 | across availability zones and they can even be provided as managed services
13 | from entirely separate AWS accounts.
14 |
15 | But they're hard to use*. This project tries to make them easier. See further
16 | down for an explanation of the difficulty.
17 |
18 | ## Example use cases
19 |
20 | These are really just intended to demonstrate that anything is possible in the
21 | world of software-defined networking. Please ping me on [Twitter][twit] with any
22 | cool ideas you have. Or any enhancements to the following ideas.
23 |
24 | * [`lambda_acceptor/lambda_acceptor.go`](/examples/lambda_acceptor/lambda_acceptor.go)
25 | takes the idea of [AWS API Gateway Lambda authorizers][apigw-auth] and applies
26 | it to VPC flows. At the start of every new connection, a Lambda function is
27 | invoked and returns a decision about whether to allow or drop the connection.
28 | It's like security groups 2.0. Input/output looks like this:
29 |
30 | 
31 |
32 | * [`cloudfront_functions/rick.js`](/examples/cloudfront_functions/rick.js) is
33 | an example of how the [CloudFront Functions][cff-model] event model can be
34 | applied to rewriting HTTP(S) requests inside a VPC. In this particular example,
35 | we're ensuring that any [AWS Workspaces][workspaces] users visiting YouTube
36 | can only watch one particular video.
37 |
38 | * [`flowdogshark/flowdogshark.go`](/examples/flowdogshark/flowdogshark.go) is an
39 | [`extcap`][extcap] plugin for Wireshark that allows you to live-attach
40 | Wireshark to flowdog and capture traffic flowing through your VPC. Given that
41 | flowdog does TLS interception (see later section in README), it can even use
42 | Wireshark's support for decoding TLS. Here's an example of intercepting the
43 | Amazon SSM agent:
44 |
45 | 
46 |
47 | * [`account_id_emf/account_id_emf.go`](/examples/account_id_emf/account_id_emf.go)
48 | is an example of scanning all AWS API calls made within the VPC for SigV4 auth
49 | headers, [extracting the AWS account ID][extract-acct-id] and emitting it to
50 | CloudWatch via specially-formatted logs that are turned into metrics. This could
51 | be used to alert on newly-seen account IDs: a potential indicator of a compromised
52 | instance.
53 |
54 | * [`upsidedown/upsidedown.go`](/examples/upsidedown/upsidedown.go) is an
55 | implementation of the classic [Upside-Down-Ternet][upsidedown]. It blurs and
56 | rotates every image 180º when browsing the net.
57 |
58 | 
59 |
60 | * [`sts_rickroll/sts_rickroll.go`](/examples/sts_rickroll/sts_rickroll.go) is
61 | another silly example. Here we are modifying the response of the AWS API call
62 | for `aws sts get-caller-identity` to return something unexpected. You could
63 | equally use the same logic to return your favourite video on every seventh
64 | object downloaded through an S3 VPC gateway.
65 |
66 | 
67 |
68 | * [`gwlb/websocket.go`](/gwlb/websocket.go) is not an example, but I got lazy.
69 | [Nick Frichette][nickf] had the great suggestion of intercepting the [SSM agent][agent]
70 | for shenanigans. This code will detect websockets and parse messages, but right
71 | now only passes them back and forth. Soon™.
72 |
73 | * TODO: You could save [HAR archives][har] of all web traffic to buckets
74 | in S3 for later perusal.
75 |
76 | ## What about TLS?
77 |
78 | As great as GWLBs are, they're not magic. We haven't broken TLS. For this app,
79 | we create a custom root certificate authority and add it to the trust store on
80 | our EC2 instances. Rather than deal in sensitive private key material, we use
81 | AWS [KMS' support for asymmetric keys][kms] for our private key.
82 | [`generate.go`](/kmssigner/generate/generate.go) creates a certificate using that
83 | key. That certificate is then stored and trusted on the OS (e.g. in Amazon Linux 2
84 | you would run `cat $CERT >> /usr/share/pki/ca-trust-source/anchors/lol.pem && update-ca-trust`)
85 |
86 | Rather than invoking KMS on every TLS connection, on launch this app creates an
87 | ephemeral key pair and certificate in memory, asks KMS to sign it and then uses
88 | that as an intermediate certificate authority. This means we can have fast TLS
89 | de/re-encryption with no stored secrets.
90 |
91 | When Wireshark is attached, flowdog can stream TLS key logs in [NSS Key Log Format][klf].
92 | This allows the Wireshark user to view all decrypted TLS traffic without giving
93 | away either the KMS private key (impossible) or intermediate CA private key (very
94 | unwise).
95 |
96 | ## Why so hard?
97 |
98 | (*) GWLBs aren't hard themselves, look at this diagram (from Amazon's [blog post][amz-blog]):
99 |
100 | 
101 |
102 | It's inspecting and modifying network traffic in general that is extremely
103 | difficult. Especially non-trivial modifications. Take the following diagram as
104 | an example. This is just one packet in a flow of packets between an EC2
105 | instance and the Internet when `curl https://google.com` is run.
106 |
107 | 
108 |
109 | You can think of this packet as having many layers. Each layer "wraps" the
110 | layer below it. The bottom six layers were sent by the EC2 instance. The top
111 | three layers are GWLB-specific. They identify which VPC endpoint (e.g. customer)
112 | the packet came from and which "flow" of packets this particular packet belongs to.
113 |
114 | Say we want to change all web requests to google.com to have the `User-Agent`
115 | request header instead be lower-case, e.g. `user-agent`. This would require us to
116 | parse the formats for:
117 |
118 | * The inner IPv4 layer, to identify is this a TCP packet
119 | * The TCP layer, to identify if the destination port is 80 or 443
120 | * The TLS layer, to (magically, for now) decrypt the payload
121 | * The HTTP/2 layer, to inspect the multiplexed streams within
122 | * The frames in each HTTP/2 stream, to identify if they are a `HEADERS` frame.
123 | * The headers in the HTTP/2 frame, to see if the `User-Agent` header is present.
124 |
125 | Finally we would have to edit the packet in memory at the right offset to change
126 | `U` to `u` and `A` to `a`, correct the checksums at every layer of the packet
127 | and re-encrypt the TLS payload. That's a lot of work.
128 |
129 | And that's a trivial change: the packet length hasn't changed. Imagine if
130 | wanted to insert a few additional headers in that request. Maybe that would
131 | push the packet length over the typical 1500 byte limit for packets on the
132 | Internet. That increases the amount of work needed by orders of magnitude: now
133 | we need to reimplement the TCP state machine, because we'll now need two packets.
134 | And those packets each need sequence numbers. But the original EC2 instance will
135 | get a response from Google for sequence numbers it didn't expect, so the
136 | connection will fail. So what we need to do is instead _terminate_ the TCP
137 | connection at the GWLB appliance and open a _new_ connection to Google from the
138 | GWLB appliance. The app will need to juggle these two TCP connections and pass
139 | the underlying data to and from Google and the EC2 instance, all while keeping
140 | the two connection's different states in sync.
141 |
142 | That's so much work that it's no wonder that even after more than a year, only
143 | massive well-funded vendors have implemented this capability. And even then, it
144 | looks like they're limited to either read-only inspection or dropping suspicious
145 | packets.
146 |
147 | 
148 |
149 | So I built this thing. It uses a handful of packages to make traffic inspection
150 | and modification accessible to even developers like you or me. Those packages
151 | are:
152 |
153 | * [`inet.af/netstack`](https://pkg.go.dev/inet.af/netstack): a reimplementation
154 | of the entire Linux TCP/IP stack in Go, extracted from the gVisor project.
155 |
156 | * [`github.com/google/gopacket`](https://pkg.go.dev/github.com/google/gopacket)
157 | to extract and parse the Geneve, IP, TCP, UDP, etc layers from the raw packets
158 | delivered by the GWLB.
159 |
160 | * [`httputil`](https://pkg.go.dev/net/http/httputil) in the Go stdlib, to
161 | reverse-proxy HTTP and HTTPS traffic and parse flows into individual request
162 | and response objects.
163 |
164 | * [`github.com/aws/aws-sdk-go`](https://pkg.go.dev/github.com/aws/aws-sdk-go) to
165 | use AWS KMS asymmetric keys for the root certificate authority that can be
166 | installed on EC2 instances for transparent TLS decryption - without having to
167 | manage a highly-sensitive private key.
168 |
169 | * [`rogchap.com/v8go`](https://pkg.go.dev/rogchap.com/v8go) to embed the V8
170 | JavaScript engine into Go, so that we can write scripts to modify traffic
171 | in JS, which is more familiar than Go to many developers.
172 |
173 | [extcap]: https://www.wireshark.org/docs/man-pages/extcap.html
174 | [klf]: https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/Key_Log_Format
175 | [twit]: https://twitter.com/__steele
176 | [kms]: https://docs.aws.amazon.com/kms/latest/developerguide/symmetric-asymmetric.html
177 | [cff-model]: https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/writing-function-code.html
178 | [workspaces]: https://aws.amazon.com/workspaces/
179 | [extract-acct-id]: https://awsteele.com/blog/2020/09/26/aws-access-key-format.html
180 | [apigw-auth]: https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-use-lambda-authorizer.html
181 | [nickf]: https://github.com/Frichetten
182 | [agent]: https://docs.aws.amazon.com/systems-manager/latest/userguide/ssm-agent.html
183 | [har]: https://en.wikipedia.org/wiki/HAR_(file_format)
184 | [amz-blog]: https://aws.amazon.com/blogs/networking-and-content-delivery/integrate-your-custom-logic-or-appliance-with-aws-gateway-load-balancer/
185 | [upsidedown]: https://www.ex-parrot.com/pete/upside-down-ternet.html
186 |
--------------------------------------------------------------------------------
/docs/authorizer.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aidansteele/flowdog/b58f75107233d557453b9bffaa8198f76a9d9ed2/docs/authorizer.png
--------------------------------------------------------------------------------
/docs/aws-diagram.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aidansteele/flowdog/b58f75107233d557453b9bffaa8198f76a9d9ed2/docs/aws-diagram.png
--------------------------------------------------------------------------------
/docs/geneve-packet-http2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aidansteele/flowdog/b58f75107233d557453b9bffaa8198f76a9d9ed2/docs/geneve-packet-http2.png
--------------------------------------------------------------------------------
/docs/sts-rickroll.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aidansteele/flowdog/b58f75107233d557453b9bffaa8198f76a9d9ed2/docs/sts-rickroll.png
--------------------------------------------------------------------------------
/docs/upside-down.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aidansteele/flowdog/b58f75107233d557453b9bffaa8198f76a9d9ed2/docs/upside-down.png
--------------------------------------------------------------------------------
/docs/vendors-aws-blog.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aidansteele/flowdog/b58f75107233d557453b9bffaa8198f76a9d9ed2/docs/vendors-aws-blog.png
--------------------------------------------------------------------------------
/docs/wireshark-demo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aidansteele/flowdog/b58f75107233d557453b9bffaa8198f76a9d9ed2/docs/wireshark-demo.png
--------------------------------------------------------------------------------
/examples/account_id_emf/account_id_emf.go:
--------------------------------------------------------------------------------
1 | package account_id_emf
2 |
3 | import (
4 | "github.com/glassechidna/go-emf/emf"
5 | "github.com/glassechidna/go-emf/emf/unit"
6 | "net/http"
7 | )
8 |
9 | type AccountIdEmf struct{}
10 |
11 | func (a *AccountIdEmf) OnRequest(req *http.Request) {
12 | auth := req.Header.Get("Authorization")
13 |
14 | parsed, ok := ParseAuthorizationHeader(auth)
15 | if !ok {
16 | return
17 | }
18 |
19 | emf.Namespace = "flowdog"
20 | emf.Emit(emf.MSI{
21 | "KeyId": parsed.KeyId,
22 | "Date": parsed.Date,
23 | "Region": parsed.Region,
24 | "Service": parsed.Service,
25 | "AccountId": emf.Dimension(parsed.AccountId),
26 | "Requests": emf.Metric(1, unit.Count),
27 | })
28 | }
29 |
30 | func (a *AccountIdEmf) OnResponse(resp *http.Response) error {
31 | return nil
32 | }
33 |
--------------------------------------------------------------------------------
/examples/account_id_emf/parse.go:
--------------------------------------------------------------------------------
1 | package account_id_emf
2 |
3 | import (
4 | "fmt"
5 | "github.com/kenshaw/baseconv"
6 | "strconv"
7 | "strings"
8 | )
9 |
10 | type ParsedAuthorizationHeader struct {
11 | KeyId string
12 | Date string
13 | Region string
14 | Service string
15 | AccountId string
16 | }
17 |
18 | func ParseAuthorizationHeader(auth string) (ParsedAuthorizationHeader, bool) {
19 | prefix := "AWS4-HMAC-SHA256 Credential="
20 | if !strings.HasPrefix(auth, prefix) {
21 | return ParsedAuthorizationHeader{}, false
22 | }
23 |
24 | s := strings.Split(strings.TrimPrefix(auth, prefix), "/")
25 | keyId, date, region, service := s[0], s[1], s[2], s[3]
26 | accountId := accountIdFromAccessKeyId(keyId)
27 | return ParsedAuthorizationHeader{
28 | KeyId: keyId,
29 | Date: date,
30 | Region: region,
31 | Service: service,
32 | AccountId: accountId,
33 | }, true
34 | }
35 |
36 | // from https://awsteele.com/blog/2020/09/26/aws-access-key-format.html
37 | func accountIdFromAccessKeyId(accessKeyId string) string {
38 | base10 := "0123456789"
39 | base32AwsFlavour := "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"
40 |
41 | offsetStr, _ := baseconv.Convert("QAAAAAAA", base32AwsFlavour, base10)
42 | offset, _ := strconv.Atoi(offsetStr)
43 |
44 | offsetAccountIdStr, _ := baseconv.Convert(accessKeyId[4:12], base32AwsFlavour, base10)
45 | offsetAccountId, _ := strconv.Atoi(offsetAccountIdStr)
46 |
47 | accountId := 2 * (offsetAccountId - offset)
48 |
49 | if strings.Index(base32AwsFlavour, accessKeyId[12:13]) >= strings.Index(base32AwsFlavour, "Q") {
50 | accountId++
51 | }
52 |
53 | return fmt.Sprintf("%012d", accountId)
54 | }
55 |
--------------------------------------------------------------------------------
/examples/cloudfront_functions/cloudfront_functions.go:
--------------------------------------------------------------------------------
1 | package cloudfront_functions
2 |
3 | import (
4 | "bytes"
5 | "context"
6 | "encoding/json"
7 | "fmt"
8 | "github.com/pkg/errors"
9 | "io/ioutil"
10 | "net/http"
11 | "net/url"
12 | "rogchap.com/v8go"
13 | "strings"
14 | )
15 |
16 | type CloudfrontFunctions struct {
17 | reqV8 *v8go.Context
18 | reqFn *v8go.Function
19 | }
20 |
21 | type cffContextKey string
22 |
23 | const cffContextKeyNewResp = cffContextKey("cffContextKeyNewResp")
24 |
25 | func NewCloudfrontFunctions(onRequest string) (*CloudfrontFunctions, error) {
26 | v8 := v8go.NewContext()
27 | _, err := v8.RunScript(onRequest, "onRequest.js")
28 | if err != nil {
29 | return nil, errors.WithStack(err)
30 | }
31 |
32 | fnv, err := v8.Global().Get("onRequest")
33 | if err != nil {
34 | return nil, errors.WithStack(err)
35 | }
36 |
37 | fn, err := fnv.AsFunction()
38 | if err != nil {
39 | return nil, errors.WithStack(err)
40 | }
41 |
42 | return &CloudfrontFunctions{reqV8: v8, reqFn: fn}, nil
43 | }
44 |
45 | func (c *CloudfrontFunctions) OnRequest(req *http.Request) {
46 | qs := map[string]cffValue{}
47 | for key, values := range req.URL.Query() {
48 | qs[key] = cffValue{Value: values[0]}
49 | }
50 |
51 | hdr := map[string]cffValue{}
52 | for key, values := range req.Header {
53 | hdr[strings.ToLower(key)] = cffValue{Value: values[0]}
54 | }
55 | hdr["host"] = cffValue{Value: req.URL.Host}
56 |
57 | ck := map[string]cffValue{}
58 |
59 | payload := cffEvent{
60 | Version: "1.0",
61 | Context: cffContext{EventType: "viewer-request"},
62 | Request: cffRequest{
63 | Method: req.Method,
64 | Uri: req.URL.Path,
65 | Querystring: qs,
66 | Headers: hdr,
67 | Cookies: ck,
68 | },
69 | }
70 |
71 | j, _ := json.Marshal(payload)
72 | val, err := v8go.JSONParse(c.reqV8, string(j))
73 | if err != nil {
74 | fmt.Printf("%+v\n", errors.WithStack(err))
75 | panic(err)
76 | }
77 |
78 | retval, err := c.reqFn.Call(c.reqV8.Global(), val)
79 | if err != nil {
80 | fmt.Printf("%+v\n", errors.WithStack(err))
81 | panic(err)
82 | }
83 |
84 | js, err := v8go.JSONStringify(c.reqV8, retval)
85 | if err != nil {
86 | fmt.Printf("%+v\n", errors.WithStack(err))
87 | panic(err)
88 | }
89 |
90 | newresp := cffResponse{}
91 | _ = json.Unmarshal([]byte(js), &newresp)
92 |
93 | if newresp.StatusCode > 0 {
94 | // TODO: this doesn't actually stop the original request from being fired.
95 | fmt.Println("editing response instead")
96 | ctx := context.WithValue(req.Context(), cffContextKeyNewResp, &newresp)
97 | reqctx := req.WithContext(ctx)
98 | *req = *reqctx
99 | } else {
100 | err = c.modifyRequest(req, js)
101 | if err != nil {
102 | fmt.Printf("%+v\n", err)
103 | panic(err)
104 | }
105 | }
106 | }
107 |
108 | func (c *CloudfrontFunctions) modifyRequest(req *http.Request, js string) error {
109 | newreq := cffRequest{}
110 | err := json.Unmarshal([]byte(js), &newreq)
111 | if err != nil {
112 | return errors.WithStack(err)
113 | }
114 |
115 | req.Method = newreq.Method
116 | req.Header = http.Header{}
117 | for key, value := range newreq.Headers {
118 | req.Header.Set(key, value.Value)
119 | }
120 |
121 | newhost := newreq.Headers["host"].Value
122 | req.URL.Host = newhost
123 | req.Host = req.URL.Host
124 | req.URL.Path = newreq.Uri
125 |
126 | q := url.Values{}
127 | for key, value := range newreq.Querystring {
128 | q.Set(key, value.Value)
129 | }
130 | req.URL.RawQuery = q.Encode()
131 |
132 | // TODO: cookies
133 | // TODO: body
134 | return nil
135 | }
136 |
137 | func (c *CloudfrontFunctions) OnResponse(resp *http.Response) error {
138 | newresp, ok := resp.Request.Context().Value(cffContextKeyNewResp).(*cffResponse)
139 | if !ok {
140 | return nil
141 | }
142 |
143 | fmt.Println("returning edited response")
144 | resp.StatusCode = newresp.StatusCode
145 | resp.Status = fmt.Sprintf("%d %s", newresp.StatusCode, newresp.StatusDescription)
146 | resp.Body = ioutil.NopCloser(bytes.NewReader([]byte{}))
147 |
148 | resp.Header = http.Header{}
149 | for key, value := range newresp.Headers {
150 | resp.Header.Set(key, value.Value)
151 | }
152 |
153 | return nil
154 | }
155 |
156 | type cffContext struct {
157 | DistributionDomainName string `json:"distributionDomainName"`
158 | DistributionId string `json:"distributionId"`
159 | EventType string `json:"eventType"`
160 | RequestId string `json:"requestId"`
161 | }
162 |
163 | type cffValue struct {
164 | Value string `json:"value"`
165 | }
166 |
167 | type cffRequest struct {
168 | Method string `json:"method"`
169 | Uri string `json:"uri"`
170 | Querystring map[string]cffValue `json:"querystring"`
171 | Headers map[string]cffValue `json:"headers"`
172 | Cookies map[string]cffValue `json:"cookies"`
173 | }
174 |
175 | type cffSetCookie struct {
176 | Value string `json:"value"`
177 | Attributes string `json:"attributes"`
178 | }
179 |
180 | type cffResponse struct {
181 | StatusCode int `json:"statusCode"`
182 | StatusDescription string `json:"statusDescription"`
183 | Headers map[string]cffValue `json:"headers"`
184 | Cookies map[string]cffSetCookie `json:"cookies"`
185 | }
186 |
187 | type cffEvent struct {
188 | Version string `json:"version"`
189 | Context cffContext `json:"context"`
190 | Request cffRequest `json:"request,omitempty"`
191 | Response cffResponse `json:"response,omitempty"`
192 | Viewer struct {
193 | Ip string `json:"ip"`
194 | } `json:"viewer"`
195 | }
196 |
--------------------------------------------------------------------------------
/examples/cloudfront_functions/cloudfront_functions_test.go:
--------------------------------------------------------------------------------
1 | package cloudfront_functions
2 |
3 | import (
4 | "github.com/stretchr/testify/require"
5 | "net/http"
6 | "testing"
7 | )
8 |
9 | func TestCloudfrontFunctions(t *testing.T) {
10 | cff, err := NewCloudfrontFunctions(`
11 | function onRequest(event) {
12 | return JSON.stringify(event.request);
13 | }
14 | `)
15 | require.NoError(t, err)
16 |
17 | req, _ := http.NewRequest("GET", "https://google.com/abc?def=ghi", nil)
18 | req.Header.Set("Authorization", "monkey")
19 | cff.OnRequest(req)
20 | }
21 |
--------------------------------------------------------------------------------
/examples/cloudfront_functions/rick.go:
--------------------------------------------------------------------------------
1 | package cloudfront_functions
2 |
3 | import (
4 | "github.com/aidansteele/flowdog/gwlb"
5 | "io/ioutil"
6 | )
7 |
8 | func NewRickroll() gwlb.Interceptor {
9 | script, _ := ioutil.ReadFile("rick.js")
10 | cff, _ := NewCloudfrontFunctions(string(script))
11 | return cff
12 | }
13 |
--------------------------------------------------------------------------------
/examples/cloudfront_functions/rick.js:
--------------------------------------------------------------------------------
1 | function onRequest(event) {
2 | const r = event.request;
3 | if (r.headers.host.value !== "www.youtube.com") {
4 | return r;
5 | }
6 |
7 | const onlyVideoOnYoutube = "https://www.youtube.com/watch?v=dQw4w9WgXcQ";
8 | const referer = r.headers.referer;
9 | if (referer && referer.value === onlyVideoOnYoutube) {
10 | return r;
11 | }
12 |
13 | if (r.uri === "/watch" && r.querystring.v.value === "dQw4w9WgXcQ") {
14 | return r;
15 | }
16 |
17 | return {
18 | statusCode: 302,
19 | statusDescription: 'Found',
20 | headers: {
21 | location: { value: onlyVideoOnYoutube }
22 | }
23 | };
24 | }
25 |
--------------------------------------------------------------------------------
/examples/flowdogshark/flowdogshark.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "github.com/aidansteele/flowdog/gwlb/shark"
7 | "github.com/google/gopacket"
8 | "github.com/google/gopacket/layers"
9 | "github.com/google/gopacket/pcapgo"
10 | "github.com/kor44/extcap"
11 | "github.com/pkg/errors"
12 | "google.golang.org/grpc"
13 | "google.golang.org/grpc/credentials/insecure"
14 | "io"
15 | "os"
16 | "strings"
17 | )
18 |
19 | func main() {
20 | // TODO: open upstream github issue about capture filter validation not working
21 | // note that program is invoked for every keystroke in capture filter textbox
22 | if len(os.Args) > 1 && os.Args[1] == "--extcap-capture-filter" {
23 | _, err := shark.FilterVM(os.Args[2])
24 | if err == nil {
25 | os.Exit(0)
26 | }
27 | }
28 |
29 | // TODO: open upstream issue about this unrecognised flag warning
30 | versionIdx := -1
31 | for idx, arg := range os.Args {
32 | if strings.HasPrefix(arg, "--extcap-version") {
33 | versionIdx = idx
34 | }
35 | }
36 | if versionIdx > 0 {
37 | os.Args = append(os.Args[:versionIdx], os.Args[versionIdx+1:]...)
38 | }
39 |
40 | app := extcap.App{
41 | Usage: "flowdogshark",
42 | HelpPage: "flowdogshark attaches to flowdog-managed AWS GWLB appliances for VPC-wide packet capture",
43 | GetInterfaces: getAllInterfaces,
44 | GetDLT: getDLT,
45 | StartCapture: startCapture,
46 | GetAllConfigOptions: func() []extcap.ConfigOption {
47 | return []extcap.ConfigOption{}
48 | },
49 | GetConfigOptions: func(iface string) ([]extcap.ConfigOption, error) {
50 | return []extcap.ConfigOption{}, nil
51 | },
52 | }
53 |
54 | app.Run(os.Args)
55 | }
56 |
57 | func getAllInterfaces() ([]extcap.CaptureInterface, error) {
58 | return []extcap.CaptureInterface{
59 | {
60 | Value: "vpce-08adcae6a2example",
61 | Display: "flowdogshark: display",
62 | },
63 | }, nil
64 | }
65 |
66 | func getDLT(iface string) (extcap.DLT, error) {
67 | return extcap.DLT{
68 | Number: int(layers.LinkTypeRaw),
69 | Name: "LINKTYPE_RAW",
70 | Display: "dlt display?",
71 | }, nil
72 | }
73 |
74 | func startCapture(iface string, pipe io.WriteCloser, filter string, opts map[string]interface{}) error {
75 | defer pipe.Close()
76 | w, err := pcapgo.NewNgWriter(pipe, layers.LinkTypeRaw)
77 | if err != nil {
78 | return errors.WithStack(err)
79 | }
80 |
81 | ctx := context.Background()
82 | conn, err := grpc.DialContext(
83 | ctx,
84 | "127.0.0.1:7081",
85 | grpc.WithTransportCredentials(insecure.NewCredentials()),
86 | )
87 | if err != nil {
88 | return errors.WithStack(err)
89 | }
90 |
91 | client := shark.NewVpcsharkClient(conn)
92 | cc, err := client.GetPackets(ctx, &shark.GetPacketsInput{
93 | Filter: filter,
94 | PacketType: shark.PacketType_PRE,
95 | })
96 | if err != nil {
97 | fmt.Printf("%+v\n", err)
98 | panic(err)
99 | }
100 |
101 | for {
102 | msg, err := cc.Recv()
103 | if err != nil {
104 | return errors.WithStack(err)
105 | }
106 |
107 | if msg.SslKeyLog != nil {
108 | err = w.WriteDecryptionSecrets(pcapgo.NgDecryptionSecrets{
109 | Type: pcapgo.NgDecryptionSecretTypeTLSKeyLog,
110 | Data: msg.SslKeyLog,
111 | })
112 | if err != nil {
113 | return errors.WithStack(err)
114 | }
115 | }
116 |
117 | if msg.Payload != nil {
118 | gpkt := gopacket.NewPacket(msg.Payload, layers.LayerTypeGeneve, gopacket.Default)
119 | geneve := gpkt.Layer(layers.LayerTypeGeneve).(*layers.Geneve)
120 | payload := geneve.LayerPayload()
121 |
122 | err = w.WritePacket(gopacket.CaptureInfo{
123 | Timestamp: msg.Time.AsTime(),
124 | CaptureLength: len(payload),
125 | Length: len(payload),
126 | InterfaceIndex: 0,
127 | }, payload)
128 | if err != nil {
129 | return errors.WithStack(err)
130 | }
131 | }
132 |
133 | err = w.Flush()
134 | if err != nil {
135 | return errors.WithStack(err)
136 | }
137 | }
138 |
139 | return nil
140 | }
141 |
--------------------------------------------------------------------------------
/examples/geneve_headers/geneve_headers.go:
--------------------------------------------------------------------------------
1 | package geneve_headers
2 |
3 | import (
4 | "fmt"
5 | "github.com/aidansteele/flowdog/gwlb"
6 | "net/http"
7 | )
8 |
9 | type GeneveHeaders struct{}
10 |
11 | func (g *GeneveHeaders) OnRequest(req *http.Request) {
12 | opts := gwlb.GeneveOptionsFromContext(req.Context())
13 | addHeaders(req.Header, opts)
14 | }
15 |
16 | func (g *GeneveHeaders) OnResponse(resp *http.Response) error {
17 | opts := gwlb.GeneveOptionsFromContext(resp.Request.Context())
18 | addHeaders(resp.Header, opts)
19 | return nil
20 | }
21 |
22 | func addHeaders(header http.Header, opts gwlb.AwsGeneveOptions) {
23 | header.Set("X-Gwlb-Vpc-Endpoint-Id", fmt.Sprintf("vpce-0%016x", opts.VpcEndpointId))
24 | header.Set("X-Gwlb-Attachment-Id", fmt.Sprintf("%016x", opts.AttachmentId))
25 | header.Set("X-Gwlb-Flow-Cookie", fmt.Sprintf("%08x", opts.FlowCookie))
26 | }
27 |
--------------------------------------------------------------------------------
/examples/lambda_acceptor/cfn.yml:
--------------------------------------------------------------------------------
1 | Transform: AWS::Serverless-2016-10-31
2 |
3 | Resources:
4 | Function:
5 | Type: AWS::Serverless::Function
6 | Properties:
7 | CodeUri: ./lambda/bootstrap
8 | Architectures: [arm64]
9 | AutoPublishAlias: live
10 | Runtime: provided.al2
11 | Handler: unused
12 | MemorySize: 512
13 | Layers:
14 | - !Sub arn:aws:lambda:${AWS::Region}:580247275435:layer:LambdaInsightsExtension-Arm64:1
15 |
16 | Outputs:
17 | Function:
18 | Value: !Ref Function.Version
19 |
--------------------------------------------------------------------------------
/examples/lambda_acceptor/enricher.go:
--------------------------------------------------------------------------------
1 | package lambda_acceptor
2 |
3 | import (
4 | "github.com/aws/aws-sdk-go/service/ec2"
5 | "github.com/aws/aws-sdk-go/service/ec2/ec2iface"
6 | "github.com/pkg/errors"
7 | )
8 |
9 | type Enricher struct {
10 | // TODO: obviously this should update live in response to eventbridge events
11 | cache map[string]*ec2.Instance
12 | }
13 |
14 | func NewEnricher(api ec2iface.EC2API) (*Enricher, error) {
15 | m := map[string]*ec2.Instance{}
16 |
17 | err := api.DescribeInstancesPages(&ec2.DescribeInstancesInput{}, func(page *ec2.DescribeInstancesOutput, lastPage bool) bool {
18 | for _, r := range page.Reservations {
19 | for _, i := range r.Instances {
20 | instance := i
21 | for _, iface := range i.NetworkInterfaces {
22 | m[*iface.PrivateIpAddress] = instance
23 | }
24 | }
25 | }
26 | return !lastPage
27 | })
28 | if err != nil {
29 | return nil, errors.WithStack(err)
30 | }
31 |
32 | return &Enricher{cache: m}, nil
33 | }
34 |
35 | func (e *Enricher) InstanceByIp(ip string) *ec2.Instance {
36 | return e.cache[ip]
37 | }
38 |
--------------------------------------------------------------------------------
/examples/lambda_acceptor/lambda/lambda.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "encoding/json"
6 | "fmt"
7 | "github.com/aidansteele/flowdog/examples/lambda_acceptor"
8 | "github.com/aws/aws-lambda-go/lambda"
9 | )
10 |
11 | func main() {
12 | lambda.Start(handle)
13 | }
14 |
15 | func handle(ctx context.Context, input *lambda_acceptor.AcceptorInput) (*lambda_acceptor.AcceptorOutput, error) {
16 | j, _ := json.Marshal(input)
17 | fmt.Println(string(j))
18 |
19 | output := &lambda_acceptor.AcceptorOutput{Accept: true}
20 | j, _ = json.Marshal(output)
21 | fmt.Println(string(j))
22 | return output, nil
23 | }
24 |
--------------------------------------------------------------------------------
/examples/lambda_acceptor/lambda_acceptor.go:
--------------------------------------------------------------------------------
1 | package lambda_acceptor
2 |
3 | import (
4 | "context"
5 | "encoding/json"
6 | "fmt"
7 | "github.com/aidansteele/flowdog/gwlb"
8 |
9 | "github.com/aws/aws-sdk-go/service/ec2/ec2iface"
10 | "github.com/aws/aws-sdk-go/service/lambda"
11 | "github.com/aws/aws-sdk-go/service/lambda/lambdaiface"
12 | "github.com/google/gopacket"
13 | "github.com/google/gopacket/layers"
14 | "github.com/pkg/errors"
15 | )
16 |
17 | type LambdaAcceptor struct {
18 | lambda lambdaiface.LambdaAPI
19 | function string
20 | enricher *Enricher
21 | }
22 |
23 | func New(lambda lambdaiface.LambdaAPI, function string, ec2 ec2iface.EC2API) (*LambdaAcceptor, error) {
24 | enricher, err := NewEnricher(ec2)
25 | if err != nil {
26 | return nil, errors.WithStack(err)
27 | }
28 |
29 | return &LambdaAcceptor{
30 | lambda: lambda,
31 | function: function,
32 | enricher: enricher,
33 | }, nil
34 | }
35 |
36 | type AcceptorInput struct {
37 | GeneveOptions GeneveOptions
38 | NetworkProtocol string
39 | TransportProtocol string
40 | Source Endpoint
41 | Destination Endpoint
42 | }
43 |
44 | type GeneveOptions struct {
45 | VpcEndpointId string
46 | FlowCookie string
47 | AttachmentId string `json:"AttachmentId,omitempty"`
48 | }
49 |
50 | type Endpoint struct {
51 | IpAddress string
52 | Port int `json:"Port,omitempty"`
53 | EC2 *Ec2Endpoint `json:"EC2,omitempty"`
54 | }
55 |
56 | type Ec2Endpoint struct {
57 | InstanceId string
58 | AccountId string
59 | VpcId string
60 | SubnetId string
61 | Tags map[string]string
62 | }
63 |
64 | type AcceptorOutput struct {
65 | Accept bool
66 | }
67 |
68 | func (a *LambdaAcceptor) AcceptFlow(ctx context.Context, pkt gopacket.Packet, opts gwlb.AwsGeneveOptions) bool {
69 | sourceIp := ""
70 | destIp := ""
71 | networkProto := ""
72 |
73 | switch network := pkt.NetworkLayer().(type) {
74 | case *layers.IPv4:
75 | networkProto = "IPv4"
76 | sourceIp = network.SrcIP.String()
77 | destIp = network.DstIP.String()
78 | case *layers.IPv6:
79 | networkProto = "IPv6"
80 | sourceIp = network.SrcIP.String()
81 | destIp = network.DstIP.String()
82 | }
83 |
84 | sourcePort := 0
85 | destPort := 0
86 | transportProto := ""
87 |
88 | switch transport := pkt.TransportLayer().(type) {
89 | case *layers.TCP:
90 | transportProto = "TCP"
91 | sourcePort = int(transport.SrcPort)
92 | destPort = int(transport.DstPort)
93 | case *layers.UDP:
94 | transportProto = "UDP"
95 | sourcePort = int(transport.SrcPort)
96 | destPort = int(transport.DstPort)
97 | }
98 |
99 | attachmentId := ""
100 | if opts.AttachmentId > 0 {
101 | attachmentId = fmt.Sprintf("%016x", opts.AttachmentId)
102 | }
103 |
104 | inputPayload, _ := json.Marshal(AcceptorInput{
105 | GeneveOptions: GeneveOptions{
106 | VpcEndpointId: fmt.Sprintf("vpce-0%016x", opts.VpcEndpointId),
107 | FlowCookie: fmt.Sprintf("%08x", opts.FlowCookie),
108 | AttachmentId: attachmentId,
109 | },
110 | NetworkProtocol: networkProto,
111 | TransportProtocol: transportProto,
112 | Source: Endpoint{
113 | IpAddress: sourceIp,
114 | Port: sourcePort,
115 | EC2: a.ec2Endpoint(sourceIp),
116 | },
117 | Destination: Endpoint{
118 | IpAddress: destIp,
119 | Port: destPort,
120 | EC2: a.ec2Endpoint(destIp),
121 | },
122 | })
123 |
124 | invoke, err := a.lambda.InvokeWithContext(ctx, &lambda.InvokeInput{
125 | FunctionName: &a.function,
126 | Payload: inputPayload,
127 | })
128 | if err != nil {
129 | fmt.Printf("lambda acceptor err %+v\n", err)
130 | return false
131 | }
132 |
133 | output := AcceptorOutput{}
134 | err = json.Unmarshal(invoke.Payload, &output)
135 | if err != nil {
136 | fmt.Printf("parsing lambda acceptor output err %+v\n", err)
137 | return false
138 | }
139 |
140 | return output.Accept
141 | }
142 |
143 | // TODO: this enriched info should be part of the ctx
144 | // earlier on so it can be used by interceptors etc
145 | func (a *LambdaAcceptor) ec2Endpoint(ip string) *Ec2Endpoint {
146 | instance := a.enricher.InstanceByIp(ip)
147 | if instance == nil {
148 | return nil
149 | }
150 |
151 | iface := instance.NetworkInterfaces[0]
152 | tags := map[string]string{}
153 | for _, tag := range instance.Tags {
154 | tags[*tag.Key] = *tag.Value
155 | }
156 |
157 | return &Ec2Endpoint{
158 | InstanceId: *instance.InstanceId,
159 | AccountId: *iface.OwnerId,
160 | VpcId: *iface.VpcId,
161 | SubnetId: *iface.SubnetId,
162 | Tags: tags,
163 | }
164 | }
165 |
--------------------------------------------------------------------------------
/examples/sts_rickroll/sts_rickroll.go:
--------------------------------------------------------------------------------
1 | package sts_rickroll
2 |
3 | import (
4 | "bytes"
5 | "fmt"
6 | "github.com/pkg/errors"
7 | "io/ioutil"
8 | "net/http"
9 | "regexp"
10 | )
11 |
12 | type StsRickroll struct{}
13 |
14 | var stsRickrollRegexp = regexp.MustCompile(`([^<]+)`)
15 |
16 | func (s *StsRickroll) OnRequest(req *http.Request) {}
17 |
18 | func (s *StsRickroll) OnResponse(resp *http.Response) error {
19 | if resp.Request.Host != "sts.amazonaws.com" {
20 | return nil
21 | }
22 |
23 | body, err := ioutil.ReadAll(resp.Body)
24 | if err != nil {
25 | return errors.WithStack(err)
26 | }
27 | defer resp.Body.Close()
28 |
29 | body = stsRickrollRegexp.ReplaceAll(body, []byte(`Never gonna give you up, never gonna let you down.`))
30 |
31 | resp.Header.Set("Content-Length", fmt.Sprintf("%d", len(body)))
32 | resp.Body = ioutil.NopCloser(bytes.NewReader(body))
33 |
34 | return nil
35 | }
36 |
--------------------------------------------------------------------------------
/examples/upsidedown/upsidedown.go:
--------------------------------------------------------------------------------
1 | package upsidedown
2 |
3 | import (
4 | "bytes"
5 | "fmt"
6 | "github.com/aidansteele/flowdog/gwlb"
7 | "github.com/disintegration/imaging"
8 | "github.com/pkg/errors"
9 | _ "image/png"
10 | "io/ioutil"
11 | "net/http"
12 | "strings"
13 | )
14 |
15 | type interceptor struct{}
16 |
17 | // all credit to http://www.ex-parrot.com/pete/upside-down-ternet.html
18 |
19 | func UpsideDown() gwlb.Interceptor {
20 | return &interceptor{}
21 | }
22 |
23 | func (i *interceptor) OnRequest(req *http.Request) {
24 | // content ranges make this prank more work
25 | req.Header.Del("Range")
26 | req.Header.Del("If-Range")
27 | }
28 |
29 | func (i *interceptor) OnResponse(resp *http.Response) error {
30 | resp.Header.Del("Accept-Ranges")
31 |
32 | contentType := resp.Header.Get("Content-Type")
33 | if !strings.HasPrefix(contentType, "image/") {
34 | return nil
35 | }
36 |
37 | originalBody := resp.Body
38 | defer originalBody.Close()
39 |
40 | img, err := imaging.Decode(originalBody)
41 | if err != nil {
42 | return errors.WithStack(err)
43 | }
44 |
45 | // perform the.. "magic"
46 | img = imaging.Rotate180(img)
47 | img = imaging.Blur(img, 1.5)
48 |
49 | newBody := &bytes.Buffer{}
50 | resp.Body = ioutil.NopCloser(newBody)
51 |
52 | // i'm lazy so lets make everything a png
53 | err = imaging.Encode(newBody, img, imaging.PNG)
54 | if err != nil {
55 | return errors.WithStack(err)
56 | }
57 |
58 | resp.Header.Set("Content-Type", "image/png")
59 | resp.Header.Set("Content-Length", fmt.Sprintf("%d", newBody.Len()))
60 | return nil
61 | }
62 |
--------------------------------------------------------------------------------
/examples/webfirehose/webfirehose.go:
--------------------------------------------------------------------------------
1 | package webfirehose
2 |
3 | import (
4 | "context"
5 | "encoding/json"
6 | "fmt"
7 | "github.com/aidansteele/flowdog/examples/account_id_emf"
8 | "github.com/aidansteele/flowdog/examples/lambda_acceptor"
9 | "github.com/aidansteele/flowdog/gwlb"
10 | "github.com/aws/aws-sdk-go/service/ec2/ec2iface"
11 | "github.com/aws/aws-sdk-go/service/firehose"
12 | "github.com/aws/aws-sdk-go/service/firehose/firehoseiface"
13 | "github.com/pkg/errors"
14 | "golang.org/x/net/publicsuffix"
15 | "net"
16 | "net/http"
17 | "time"
18 | )
19 |
20 | type requestRecord struct {
21 | TLS *requestRecordTls `json:"TLS,omitempty"`
22 | AWS *requestRecordAws `json:"AWS,omitempty"`
23 | Geneve requestRecordGeneve
24 |
25 | Time time.Time
26 | Method string
27 | Host string
28 | ETldPlusOne string
29 | URL string
30 | RequestHeaders map[string]string
31 | Client lambda_acceptor.Endpoint
32 |
33 | StatusCode int
34 | StatusMessage string
35 | ResponseHeaders map[string]string
36 | Server lambda_acceptor.Endpoint
37 | }
38 |
39 | type requestRecordAws struct {
40 | AuthorizationHeader account_id_emf.ParsedAuthorizationHeader
41 | }
42 |
43 | type requestRecordTls struct {
44 | Version uint16
45 | CipherSuite uint16
46 | }
47 |
48 | type requestRecordGeneve struct {
49 | VpcEndpointId string
50 | AttachmentId string
51 | FlowCookie string
52 | }
53 |
54 | type WebFirehose struct {
55 | enricher *lambda_acceptor.Enricher
56 | firehose firehoseiface.FirehoseAPI
57 | stream string
58 | entries chan []byte
59 | }
60 |
61 | func New(firehose firehoseiface.FirehoseAPI, ec2 ec2iface.EC2API, stream string) *WebFirehose {
62 | enricher, err := lambda_acceptor.NewEnricher(ec2)
63 | if err != nil {
64 | fmt.Printf("%+v\n", err)
65 | panic(err)
66 | }
67 |
68 | return &WebFirehose{
69 | firehose: firehose,
70 | stream: stream,
71 | enricher: enricher,
72 | entries: make(chan []byte),
73 | }
74 | }
75 |
76 | func (wf *WebFirehose) Run(ctx context.Context) error {
77 | batch := []*firehose.Record{}
78 | size := 0
79 | ticker := time.NewTicker(time.Second)
80 |
81 | for {
82 | select {
83 | case <-ctx.Done():
84 | return ctx.Err()
85 | case <-ticker.C:
86 | if len(batch) == 0 {
87 | continue
88 | }
89 |
90 | // TODO: check batch count, combined batch size, etc
91 | _, err := wf.firehose.PutRecordBatchWithContext(ctx, &firehose.PutRecordBatchInput{
92 | DeliveryStreamName: &wf.stream,
93 | Records: batch,
94 | })
95 | if err != nil {
96 | return errors.WithStack(err)
97 | }
98 |
99 | fmt.Printf("firehose dump: count=%d bytes=%d\n", len(batch), size)
100 | size = 0
101 | batch = nil
102 | case entry := <-wf.entries:
103 | fmt.Printf(string(entry))
104 | record := &firehose.Record{Data: entry}
105 | batch = append(batch, record)
106 | size += len(record.Data)
107 | }
108 | }
109 | }
110 |
111 | func (wf *WebFirehose) OnRequest(req *http.Request) {
112 | }
113 |
114 | func (wf *WebFirehose) OnResponse(resp *http.Response) error {
115 | ctx := resp.Request.Context()
116 | geneve := gwlb.GeneveOptionsFromContext(ctx)
117 |
118 | source, dest := gwlb.AddrsFromContext(ctx)
119 | var client, server *net.TCPAddr
120 |
121 | if source.Port == 443 || source.Port == 80 {
122 | server = source
123 | client = dest
124 | } else {
125 | server = dest
126 | client = source
127 | }
128 |
129 | var tlsrr *requestRecordTls
130 | if server.Port == 443 {
131 | tlsrr = &requestRecordTls{
132 | Version: resp.Request.TLS.Version,
133 | CipherSuite: resp.Request.TLS.CipherSuite,
134 | }
135 | }
136 |
137 | var awsrr *requestRecordAws
138 | if parsed, ok := account_id_emf.ParseAuthorizationHeader(resp.Request.Header.Get("Authorization")); ok {
139 | awsrr = &requestRecordAws{
140 | AuthorizationHeader: parsed,
141 | }
142 | }
143 |
144 | host := resp.Request.URL.Host
145 | etldp1, _ := publicsuffix.EffectiveTLDPlusOne(host)
146 |
147 | rr := requestRecord{
148 | TLS: tlsrr,
149 | Geneve: requestRecordGeneve{
150 | VpcEndpointId: fmt.Sprintf("vpce-0%016x", geneve.VpcEndpointId),
151 | AttachmentId: fmt.Sprintf("%016x", geneve.AttachmentId),
152 | FlowCookie: fmt.Sprintf("%08x", geneve.FlowCookie),
153 | },
154 | AWS: awsrr,
155 | Time: time.Now(),
156 | Method: resp.Request.Method,
157 | Host: host,
158 | ETldPlusOne: etldp1,
159 | URL: resp.Request.URL.String(),
160 | RequestHeaders: flattenHeaders(resp.Request.Header),
161 | Client: lambda_acceptor.Endpoint{
162 | IpAddress: client.IP.String(),
163 | Port: client.Port,
164 | EC2: wf.ec2Endpoint(client.IP.String()),
165 | },
166 |
167 | StatusCode: resp.StatusCode,
168 | StatusMessage: resp.Status,
169 | ResponseHeaders: flattenHeaders(resp.Header),
170 | Server: lambda_acceptor.Endpoint{
171 | IpAddress: server.IP.String(),
172 | Port: server.Port,
173 | EC2: wf.ec2Endpoint(server.IP.String()),
174 | },
175 | }
176 |
177 | rrj, _ := json.Marshal(rr)
178 | wf.entries <- append(rrj, '\n')
179 |
180 | return nil
181 | }
182 |
183 | // TODO: this enriched info should be part of the ctx
184 | // earlier on so it can be used by interceptors etc
185 | func (wf *WebFirehose) ec2Endpoint(ip string) *lambda_acceptor.Ec2Endpoint {
186 | instance := wf.enricher.InstanceByIp(ip)
187 | if instance == nil {
188 | return nil
189 | }
190 |
191 | iface := instance.NetworkInterfaces[0]
192 | tags := map[string]string{}
193 | for _, tag := range instance.Tags {
194 | tags[*tag.Key] = *tag.Value
195 | }
196 |
197 | return &lambda_acceptor.Ec2Endpoint{
198 | InstanceId: *instance.InstanceId,
199 | AccountId: *iface.OwnerId,
200 | VpcId: *iface.VpcId,
201 | SubnetId: *iface.SubnetId,
202 | Tags: tags,
203 | }
204 | }
205 |
206 | func flattenHeaders(head http.Header) map[string]string {
207 | flat := map[string]string{}
208 |
209 | for key, vals := range head {
210 | flat[key] = vals[0]
211 | }
212 |
213 | return flat
214 | }
215 |
--------------------------------------------------------------------------------
/examples/webfirehose/webfirehose.yml:
--------------------------------------------------------------------------------
1 | Resources:
2 | Firehose:
3 | Type: AWS::KinesisFirehose::DeliveryStream
4 | Properties:
5 | DeliveryStreamType: DirectPut
6 | ExtendedS3DestinationConfiguration:
7 | BucketARN: !Sub arn:aws:s3:::${Bucket}
8 | RoleARN: !GetAtt Role.Arn
9 | Prefix: output/!{timestamp:yyyy/MM/dd}/!{partitionKeyFromQuery:etldp1}/
10 | ErrorOutputPrefix: errors/!{firehose:error-output-type}/!{timestamp:yyyy/MM/dd}/
11 | CompressionFormat: GZIP
12 | BufferingHints:
13 | IntervalInSeconds: 60
14 | SizeInMBs: 128
15 | DynamicPartitioningConfiguration:
16 | Enabled: true
17 | RetryOptions:
18 | DurationInSeconds: 300
19 | ProcessingConfiguration:
20 | Enabled: true
21 | Processors:
22 | - Type: MetadataExtraction
23 | Parameters:
24 | - ParameterName: MetadataExtractionQuery
25 | ParameterValue: "{etldp1:.ETldPlusOne}"
26 | - ParameterName: JsonParsingEngine
27 | ParameterValue: JQ-1.6
28 |
29 | Bucket:
30 | Type: AWS::S3::Bucket
31 | DeletionPolicy: Retain
32 |
33 | Role:
34 | Type: AWS::IAM::Role
35 | Properties:
36 | AssumeRolePolicyDocument:
37 | Version: 2012-10-17
38 | Statement:
39 | - Effect: Allow
40 | Principal:
41 | Service: firehose.amazonaws.com
42 | Action: sts:AssumeRole
43 | Condition:
44 | StringEquals:
45 | sts:ExternalId: !Ref AWS::AccountId
46 | Policies:
47 | - PolicyName: Firehose
48 | PolicyDocument:
49 | Version: 2012-10-17
50 | Statement:
51 | - Effect: Allow
52 | Action:
53 | - s3:AbortMultipartUpload
54 | - s3:GetBucketLocation
55 | - s3:GetObject
56 | - s3:ListBucket
57 | - s3:ListBucketMultipartUploads
58 | - s3:PutObject
59 | Resource:
60 | - !Sub arn:aws:s3:::${Bucket}
61 | - !Sub arn:aws:s3:::${Bucket}/*
62 |
63 | Outputs:
64 | Firehose:
65 | Value: !Ref Firehose
66 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/aidansteele/flowdog
2 |
3 | go 1.15
4 |
5 | require (
6 | github.com/aws/aws-lambda-go v1.28.0
7 | github.com/aws/aws-sdk-go v1.42.31
8 | github.com/davecgh/go-spew v1.1.1
9 | github.com/disintegration/imaging v1.6.2
10 | github.com/glassechidna/go-emf v0.0.0-20220102031255-2c11928b55f0
11 | github.com/google/btree v1.0.1 // indirect
12 | github.com/google/gopacket v1.1.19
13 | github.com/google/pprof v0.0.0-20220128192902-513e8ac6eea1 // indirect
14 | github.com/gorilla/websocket v1.4.2
15 | github.com/kenshaw/baseconv v0.1.1
16 | github.com/kor44/extcap v0.0.0-20201215145008-71f6cf07bb46
17 | github.com/pkg/errors v0.9.1
18 | github.com/stretchr/testify v1.7.0
19 | github.com/tidwall/spinlock v0.1.0
20 | golang.org/x/image v0.0.0-20211028202545-6944b10bf410 // indirect
21 | golang.org/x/net v0.0.0-20220225172249-27dd8689420f // indirect
22 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
23 | golang.org/x/sys v0.0.0-20220209214540-3681064d5158 // indirect
24 | golang.org/x/text v0.3.7 // indirect
25 | golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11 // indirect
26 | google.golang.org/genproto v0.0.0-20220112215332-a9c7c0acf9f2 // indirect
27 | google.golang.org/grpc v1.43.0
28 | google.golang.org/protobuf v1.27.1
29 | gopkg.in/DataDog/dd-trace-go.v1 v1.36.0
30 | inet.af/netstack v0.0.0-20211120045802-8aa80cf23d3c
31 | rogchap.com/v8go v0.7.0
32 | )
33 |
34 | replace (
35 | github.com/google/gopacket v1.1.19 => ./forks/gopacket
36 | rogchap.com/v8go v0.7.0 => github.com/rogchap/v8go v0.7.1-0.20220106173329-ede7cee433be
37 | )
38 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
2 | cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
3 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
4 | github.com/DataDog/datadog-agent/pkg/obfuscate v0.0.0-20211129110424-6491aa3bf583 h1:3nVO1nQyh64IUY6BPZUpMYMZ738Pu+LsMt3E0eqqIYw=
5 | github.com/DataDog/datadog-agent/pkg/obfuscate v0.0.0-20211129110424-6491aa3bf583/go.mod h1:EP9f4GqaDJyP1F5jTNMtzdIpw3JpNs3rMSJOnYywCiw=
6 | github.com/DataDog/datadog-go v4.8.2+incompatible h1:qbcKSx29aBLD+5QLvlQZlGmRMF/FfGqFLFev/1TDzRo=
7 | github.com/DataDog/datadog-go v4.8.2+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
8 | github.com/DataDog/datadog-go/v5 v5.0.2 h1:UFtEe7662/Qojxkw1d6SboAeA0CPI3naKhVASwFn+04=
9 | github.com/DataDog/datadog-go/v5 v5.0.2/go.mod h1:ZI9JFB4ewXbw1sBnF4sxsR2k1H3xjV+PUAOUsHvKpcU=
10 | github.com/DataDog/gostackparse v0.5.0 h1:jb72P6GFHPHz2W0onsN51cS3FkaMDcjb0QzgxxA4gDk=
11 | github.com/DataDog/gostackparse v0.5.0/go.mod h1:lTfqcJKqS9KnXQGnyQMCugq3u1FP6UZMfWR0aitKFMM=
12 | github.com/DataDog/sketches-go v1.0.0 h1:chm5KSXO7kO+ywGWJ0Zs6tdmWU8PBXSbywFVciL6BG4=
13 | github.com/DataDog/sketches-go v1.0.0/go.mod h1:O+XkJHWk9w4hDwY2ZUDU31ZC9sNYlYo8DiFsxjYeo1k=
14 | github.com/Microsoft/go-winio v0.5.0/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84=
15 | github.com/Microsoft/go-winio v0.5.1 h1:aPJp2QD7OOrhO5tQXqQoGSJc+DjDtWTGLOmNyAm6FgY=
16 | github.com/Microsoft/go-winio v0.5.1/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84=
17 | github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
18 | github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
19 | github.com/aws/aws-lambda-go v1.28.0 h1:fZiik1PZqW2IyAN4rj+Y0UBaO1IDFlsNo9Zz/XnArK4=
20 | github.com/aws/aws-lambda-go v1.28.0/go.mod h1:jJmlefzPfGnckuHdXX7/80O3BvUUi12XOkbv4w9SGLU=
21 | github.com/aws/aws-sdk-go v1.42.31 h1:tSv/YzjrFlbSqWmov9quBxrSNXLPUjJI7nPEB57S1+M=
22 | github.com/aws/aws-sdk-go v1.42.31/go.mod h1:OGr6lGMAKGlG9CVrYnWYDKIyb829c6EVBRjxqjmPepc=
23 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
24 | github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
25 | github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
26 | github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
27 | github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
28 | github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
29 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
30 | github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
31 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
32 | github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
33 | github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
34 | github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
35 | github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
36 | github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
37 | github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
38 | github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
39 | github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
40 | github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM=
41 | github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
42 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
43 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
44 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
45 | github.com/dgraph-io/ristretto v0.1.0 h1:Jv3CGQHp9OjuMBSne1485aDpUkTKEcUqF+jm/LuerPI=
46 | github.com/dgraph-io/ristretto v0.1.0/go.mod h1:fux0lOrBhrVCJd3lcTHsIJhq1T2rokOu6v9Vcb3Q9ug=
47 | github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
48 | github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c=
49 | github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
50 | github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
51 | github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
52 | github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
53 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
54 | github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
55 | github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
56 | github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
57 | github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
58 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
59 | github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
60 | github.com/glassechidna/go-emf v0.0.0-20220102031255-2c11928b55f0 h1:JJjK59ANTLUR+tHran8Q710jESoculect7MOIgwmx+I=
61 | github.com/glassechidna/go-emf v0.0.0-20220102031255-2c11928b55f0/go.mod h1:Uj0udAoNiziSRqHaOn0SjnlKmfB1Owu6p9B5oFEzwhE=
62 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
63 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
64 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
65 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
66 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
67 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
68 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
69 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
70 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
71 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
72 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
73 | github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
74 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
75 | github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
76 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
77 | github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
78 | github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
79 | github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4=
80 | github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA=
81 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
82 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
83 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
84 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
85 | github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
86 | github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
87 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
88 | github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
89 | github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8=
90 | github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo=
91 | github.com/google/pprof v0.0.0-20210423192551-a2663126120b/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
92 | github.com/google/pprof v0.0.0-20220128192902-513e8ac6eea1 h1:fBq9NM3cpLvsvc+RkweQROGEeYx7QEk35Htz7bAmyuM=
93 | github.com/google/pprof v0.0.0-20220128192902-513e8ac6eea1/go.mod h1:KgnwoLYCZ8IQu3XUZ8Nc/bM9CCZFOyjUNOSygVozoDg=
94 | github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
95 | github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
96 | github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
97 | github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
98 | github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
99 | github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
100 | github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
101 | github.com/ianlancetaylor/demangle v0.0.0-20210905161508-09a460cdf81d/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w=
102 | github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
103 | github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
104 | github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
105 | github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
106 | github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
107 | github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
108 | github.com/kenshaw/baseconv v0.1.1 h1:oAu/C7ipUT2PqT9DT0mZDGDg4URIglizZMjPv9oCu0E=
109 | github.com/kenshaw/baseconv v0.1.1/go.mod h1:yy9zGmnnR6vgOxOQb702nVdAG30JhyYZpj/5/m0siRI=
110 | github.com/kor44/extcap v0.0.0-20201215145008-71f6cf07bb46 h1:JYYyCLuPEImJw4upnIY6SPSJUZv2fjd+ovLK9DbK2Og=
111 | github.com/kor44/extcap v0.0.0-20201215145008-71f6cf07bb46/go.mod h1:H7/WxrChVI6uldeNImZeE6qDy/wuxXD2A//yL5ZernI=
112 | github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
113 | github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
114 | github.com/philhofer/fwd v1.1.1 h1:GdGcTjf5RNAxwS4QLsiMzJYj5KEvPJD3Abr261yRQXQ=
115 | github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
116 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
117 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
118 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
119 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
120 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
121 | github.com/rogchap/v8go v0.7.1-0.20220106173329-ede7cee433be h1:4SlkuW7JbBKpqkyCzecWXRz3QkDQB8hSMgqF0KaUTzc=
122 | github.com/rogchap/v8go v0.7.1-0.20220106173329-ede7cee433be/go.mod h1:MxgP3pL2MW4dpme/72QRs8sgNMmM0pRc8DPhcuLWPAs=
123 | github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
124 | github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
125 | github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
126 | github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
127 | github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
128 | github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
129 | github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
130 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
131 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
132 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
133 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
134 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
135 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
136 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
137 | github.com/tidwall/spinlock v0.1.0 h1:McCloMBXOyGiQWG1D5kHOzSA50u1UV/g3P2SAiuvnPA=
138 | github.com/tidwall/spinlock v0.1.0/go.mod h1:tbnEyV8RKQ/S4PpqY6bxHz1TWomMPQ+YkJLcP2DByiA=
139 | github.com/tinylib/msgp v1.1.2 h1:gWmO7n0Ys2RBEb7GPYB9Ujq8Mk5p2U08lRnmMcGy6BQ=
140 | github.com/tinylib/msgp v1.1.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
141 | github.com/urfave/cli/v2 v2.2.0 h1:JTTnM6wKzdA0Jqodd966MVj4vWbbquZykeX1sKbe2C4=
142 | github.com/urfave/cli/v2 v2.2.0/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ=
143 | github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE=
144 | github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU=
145 | github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
146 | go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
147 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
148 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
149 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
150 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
151 | golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
152 | golang.org/x/image v0.0.0-20211028202545-6944b10bf410 h1:hTftEOvwiOq2+O8k2D5/Q7COC7k5Qcrgc2TFURJYnvQ=
153 | golang.org/x/image v0.0.0-20211028202545-6944b10bf410/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
154 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
155 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
156 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
157 | golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
158 | golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
159 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
160 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
161 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
162 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
163 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
164 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
165 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
166 | golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
167 | golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
168 | golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
169 | golang.org/x/net v0.0.0-20220111093109-d55c255bac03 h1:0FB83qp0AzVJm+0wcIlauAjJ+tNdh7jLuacRYCIVv7s=
170 | golang.org/x/net v0.0.0-20220111093109-d55c255bac03/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
171 | golang.org/x/net v0.0.0-20220225172249-27dd8689420f h1:oA4XRj0qtSt8Yo1Zms0CUlsT3KG69V2UGQWPBxujDmc=
172 | golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
173 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
174 | golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
175 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
176 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
177 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
178 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
179 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
180 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
181 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
182 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
183 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
184 | golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
185 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
186 | golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
187 | golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
188 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
189 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
190 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
191 | golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
192 | golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
193 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
194 | golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
195 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
196 | golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
197 | golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
198 | golang.org/x/sys v0.0.0-20220111092808-5a964db01320 h1:0jf+tOCoZ3LyutmCOWpVni1chK4VfFLhRsDK7MhqGRY=
199 | golang.org/x/sys v0.0.0-20220111092808-5a964db01320/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
200 | golang.org/x/sys v0.0.0-20220209214540-3681064d5158 h1:rm+CHSpPEEW2IsXUib1ThaHIjuBVZjxNgSKmBLFfD4c=
201 | golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
202 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
203 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
204 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
205 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
206 | golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
207 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
208 | golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
209 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
210 | golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11 h1:GZokNIeuVkl3aZHJchRrr13WCsols02MLUcz1U9is6M=
211 | golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
212 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
213 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
214 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
215 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
216 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
217 | golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
218 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
219 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
220 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
221 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
222 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
223 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
224 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
225 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
226 | google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
227 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
228 | google.golang.org/genproto v0.0.0-20220112215332-a9c7c0acf9f2 h1:z+R4M/SuyaRsj1zu3WC+nIQyfSrSIpuDcY01/R3uCtg=
229 | google.golang.org/genproto v0.0.0-20220112215332-a9c7c0acf9f2/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
230 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
231 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
232 | google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
233 | google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
234 | google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
235 | google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
236 | google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
237 | google.golang.org/grpc v1.43.0 h1:Eeu7bZtDZ2DpRCsLhUlcrLnvYaMK1Gz86a+hMVvELmM=
238 | google.golang.org/grpc v1.43.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
239 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
240 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
241 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
242 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
243 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
244 | google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
245 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
246 | google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
247 | google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
248 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
249 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
250 | google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ=
251 | google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
252 | gopkg.in/DataDog/dd-trace-go.v1 v1.36.0 h1:t2KEcCXajtchpvoIGm0xU+Ytj8KkRyxsXVhWOGg6lEk=
253 | gopkg.in/DataDog/dd-trace-go.v1 v1.36.0/go.mod h1:Cv0Bzs/zTzzrUDSw8Q+q/vC+uwPD+R530npGo0lfiCE=
254 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
255 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
256 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
257 | gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
258 | gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
259 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
260 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
261 | gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ=
262 | gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
263 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
264 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
265 | inet.af/netstack v0.0.0-20211120045802-8aa80cf23d3c h1:nr31qYr+91rWD8klUkPx3eGTZzumCC414UJG1QRKZTc=
266 | inet.af/netstack v0.0.0-20211120045802-8aa80cf23d3c/go.mod h1:KOJdAzQzMLKzwFEdOOnrnSrLIhaFVB+NQoME/e5wllA=
267 |
--------------------------------------------------------------------------------
/gwlb/active_flow.go:
--------------------------------------------------------------------------------
1 | package gwlb
2 |
3 | import (
4 | "bytes"
5 | "context"
6 | "crypto/tls"
7 | "fmt"
8 | "github.com/aidansteele/flowdog/gwlb/mirror"
9 | "github.com/aidansteele/flowdog/mytls"
10 | "github.com/google/gopacket"
11 | "github.com/google/gopacket/layers"
12 | "github.com/pkg/errors"
13 | "golang.org/x/sync/errgroup"
14 | "inet.af/netstack/tcpip"
15 | "inet.af/netstack/tcpip/adapters/gonet"
16 | "inet.af/netstack/tcpip/buffer"
17 | "inet.af/netstack/tcpip/header"
18 | "inet.af/netstack/tcpip/link/channel"
19 | "inet.af/netstack/tcpip/network/ipv4"
20 | "inet.af/netstack/tcpip/stack"
21 | "inet.af/netstack/tcpip/transport/tcp"
22 | "io"
23 | "net"
24 | "net/http"
25 | "time"
26 | )
27 |
28 | const (
29 | timeoutTcp = 350 * time.Second
30 | timeoutNonTcp = 120 * time.Second
31 | )
32 |
33 | var errTimeout = errors.New("gwlb timeout")
34 |
35 | type activeFlow struct {
36 | geneveHeader []byte
37 | gwlbConn *net.UDPConn
38 | mirror chan mirror.Packet
39 | endpoint *channel.Endpoint
40 | stack *stack.Stack
41 | httpReady chan struct{}
42 | }
43 |
44 | type FlowAcceptor interface {
45 | AcceptFlow(ctx context.Context, pkt gopacket.Packet, opts AwsGeneveOptions) bool
46 | }
47 |
48 | type newFlowOptions struct {
49 | acceptor FlowAcceptor
50 | handler http.Handler
51 | keyLogger io.Writer
52 | mirror chan mirror.Packet
53 | }
54 |
55 | func newFlow(ctx context.Context, ch chan genevePacket, geneveOpts AwsGeneveOptions, options newFlowOptions) {
56 | ctx = ContextWithGeneveOptions(ctx, geneveOpts)
57 |
58 | // retrieve first packet in flow to inspect ip, port, etc
59 | pkt := <-ch
60 | // then we reinject for forwarding/interception/whatever
61 | go func() { ch <- pkt }()
62 |
63 | if options.acceptor != nil && !options.acceptor.AcceptFlow(ctx, pkt.pkt, geneveOpts) {
64 | return // TODO: this should probably send a refusal (for tcp) instead of silent dropping
65 | }
66 |
67 | fmt.Printf("%s new flow vpcEndpointId=%016x attachmentId=%016x flowCookie=%08x\n", time.Now().String(), geneveOpts.VpcEndpointId, geneveOpts.AttachmentId, geneveOpts.FlowCookie)
68 | gwlbConn := getUdpConn(pkt.addr)
69 |
70 | ipLayer, isIpv4 := pkt.pkt.Layer(layers.LayerTypeIPv4).(*layers.IPv4)
71 | tcpLayer, isTcp := pkt.pkt.TransportLayer().(*layers.TCP)
72 |
73 | if !isTcp {
74 | fmt.Printf("fast-path for non-tcp flow=%08x\n", geneveOpts.FlowCookie)
75 | fastPath(ctx, ch, gwlbConn, options.mirror, timeoutNonTcp)
76 | return
77 | }
78 |
79 | if !isIpv4 {
80 | fmt.Printf("fast-path for non-ipv4 flow=%08x\n", geneveOpts.FlowCookie)
81 | fastPath(ctx, ch, gwlbConn, options.mirror, timeoutTcp)
82 | return
83 | }
84 |
85 | if options.handler == nil || (tcpLayer.DstPort != 80 && tcpLayer.DstPort != 443) {
86 | fmt.Printf("fast-path for non-port 80/443 flow=%08x\n", geneveOpts.FlowCookie)
87 | fastPath(ctx, ch, gwlbConn, options.mirror, timeoutTcp)
88 | return
89 | }
90 |
91 | geneveLayer := pkt.pkt.Layer(layers.LayerTypeGeneve).(*layers.Geneve)
92 | contents := geneveLayer.LayerContents()
93 | hdr := make([]byte, len(contents))
94 | copy(hdr, contents)
95 |
96 | sourceAddr := &net.TCPAddr{IP: ipLayer.SrcIP, Port: int(tcpLayer.SrcPort)}
97 | destAddr := &net.TCPAddr{IP: ipLayer.DstIP, Port: int(tcpLayer.DstPort)}
98 | ctx = ContextWithAddrs(ctx, sourceAddr, destAddr)
99 |
100 | endpoint, netstack := newEndpointAndStack(geneveOpts)
101 | ctx = ContextWithNetstack(ctx, netstack)
102 |
103 | a := &activeFlow{
104 | geneveHeader: hdr,
105 | gwlbConn: gwlbConn,
106 | endpoint: endpoint,
107 | mirror: options.mirror,
108 | stack: netstack,
109 | httpReady: make(chan struct{}, 1),
110 | }
111 |
112 | g, ctx := errgroup.WithContext(ctx)
113 |
114 | g.Go(func() error {
115 | return a.runNetstackToGwlb(ctx)
116 | })
117 |
118 | g.Go(func() error {
119 | config := mytls.TlsConfig().Clone()
120 | config.KeyLogWriter = options.keyLogger
121 | return a.runProxyHttp(ctx, options.handler, config)
122 | })
123 |
124 | g.Go(func() error {
125 | <-a.httpReady
126 | return a.runGwlbToNetstack(ctx, ch, timeoutTcp)
127 | })
128 |
129 | err := g.Wait()
130 | if err == errTimeout {
131 | return
132 | }
133 |
134 | if err != nil {
135 | fmt.Printf("%+v\n", err)
136 | panic(err)
137 | }
138 | }
139 |
140 | func (a *activeFlow) runGwlbToNetstack(ctx context.Context, ch chan genevePacket, timeout time.Duration) error {
141 | originatorAddr, _ := AddrsFromContext(ctx)
142 | originator := originatorAddr.IP.String()
143 |
144 | for {
145 | select {
146 | case <-ctx.Done():
147 | return ctx.Err()
148 | case <-time.After(timeout):
149 | return errTimeout
150 | case pkt := <-ch:
151 | geneveLayer := pkt.pkt.Layer(layers.LayerTypeGeneve).(*layers.Geneve)
152 | payload := geneveLayer.LayerPayload()
153 |
154 | // TODO ipv6
155 | ipLayer := pkt.pkt.NetworkLayer().(*layers.IPv4)
156 |
157 | // TODO: avoid stringification in hot path
158 | a.mirror <- mirror.New(pkt.buf, ipLayer.SrcIP.String() == originator)
159 |
160 | data := buffer.NewVectorisedView(1, []buffer.View{payload})
161 | newPkt := stack.NewPacketBuffer(stack.PacketBufferOptions{Data: data})
162 | a.endpoint.InjectInbound(ipv4.ProtocolNumber, newPkt)
163 | newPkt.DecRef()
164 | }
165 | }
166 | }
167 |
168 | func (a *activeFlow) runNetstackToGwlb(ctx context.Context) error {
169 | buf := &bytes.Buffer{}
170 | originatorAddr, _ := AddrsFromContext(ctx)
171 | originator := originatorAddr.IP.String()
172 |
173 | for {
174 | pinfo, more := a.endpoint.ReadContext(ctx)
175 | if !more {
176 | fmt.Printf("exiting flow with cookie %08x\n", GeneveOptionsFromContext(ctx).FlowCookie)
177 | return ctx.Err()
178 | }
179 |
180 | data := buffer.NewVectorisedView(pinfo.Pkt.Size(), pinfo.Pkt.Views())
181 | view := data.ToView()
182 |
183 | buf.Write(a.geneveHeader)
184 | buf.Write(view)
185 | _, err := a.gwlbConn.Write(buf.Bytes())
186 | if err != nil {
187 | return errors.WithStack(err)
188 | }
189 |
190 | // TODO: avoid stringification in hot path
191 | a.mirror <- mirror.New(buf.Bytes(), pinfo.Route.RemoteAddress.String() == originator)
192 | buf.Reset()
193 | }
194 | }
195 |
196 | func (a *activeFlow) runProxyHttp(ctx context.Context, handler http.Handler, config *tls.Config) error {
197 | g, ctx := errgroup.WithContext(ctx)
198 |
199 | httpSrv := &http.Server{
200 | Handler: handler,
201 | TLSConfig: config,
202 | BaseContext: func(listener net.Listener) context.Context { return ctx },
203 | }
204 |
205 | httpSrv.TLSConfig.NextProtos = []string{"h2", "http/1.1"}
206 |
207 | port443, err := gonet.ListenTCP(a.stack, tcpip.FullAddress{Port: 443}, ipv4.ProtocolNumber)
208 | if err != nil {
209 | return errors.WithStack(err)
210 | }
211 |
212 | port80, err := gonet.ListenTCP(a.stack, tcpip.FullAddress{Port: 80}, ipv4.ProtocolNumber)
213 | if err != nil {
214 | return errors.WithStack(err)
215 | }
216 |
217 | a.httpReady <- struct{}{}
218 |
219 | g.Go(func() error {
220 | err := httpSrv.Serve(port80)
221 | return errors.WithStack(err)
222 | })
223 |
224 | g.Go(func() error {
225 | err := httpSrv.ServeTLS(port443, "", "")
226 | return errors.WithStack(err)
227 | })
228 |
229 | g.Go(func() error {
230 | <-ctx.Done()
231 | return httpSrv.Close()
232 | })
233 |
234 | return g.Wait()
235 | }
236 |
237 | func newEndpointAndStack(opts AwsGeneveOptions) (*channel.Endpoint, *stack.Stack) {
238 | linkAddr, _ := tcpip.ParseMACAddress("aa:bb:cc:dd:ee:ff")
239 | endpoint := channel.New(200, 8500, linkAddr)
240 |
241 | s := stack.New(stack.Options{
242 | NetworkProtocols: []stack.NetworkProtocolFactory{ipv4.NewProtocol},
243 | TransportProtocols: []stack.TransportProtocolFactory{tcp.NewProtocol},
244 | })
245 |
246 | // Add default route.
247 | s.SetRouteTable([]tcpip.Route{
248 | {NIC: NICID, Destination: header.IPv4EmptySubnet},
249 | })
250 |
251 | linkEndpoint := endpoint
252 | //linkEndpoint := sniffer.NewWithPrefix(endpoint, fmt.Sprintf("flow-%08x ", opts.flowCookie))
253 | tcpErr := s.CreateNIC(NICID, linkEndpoint)
254 | if tcpErr != nil {
255 | fmt.Printf("%+v\n", tcpErr)
256 | panic(tcpErr)
257 | }
258 |
259 | s.SetPromiscuousMode(NICID, true)
260 | s.SetSpoofing(NICID, true)
261 | return endpoint, s
262 | }
263 |
264 | const NICID = 1
265 |
--------------------------------------------------------------------------------
/gwlb/context.go:
--------------------------------------------------------------------------------
1 | package gwlb
2 |
3 | import (
4 | "context"
5 | "inet.af/netstack/tcpip/stack"
6 | "net"
7 | )
8 |
9 | type contextKey string
10 |
11 | const (
12 | contextKeyGeneveOptions = contextKey("contextKeyGeneveOptions")
13 | contextKeyAddrs = contextKey("contextKeyAddrs")
14 | contextKeyNetstack = contextKey("contextKeyNetstack")
15 | )
16 |
17 | func ContextWithGeneveOptions(ctx context.Context, opts AwsGeneveOptions) context.Context {
18 | return context.WithValue(ctx, contextKeyGeneveOptions, opts)
19 | }
20 |
21 | func GeneveOptionsFromContext(ctx context.Context) AwsGeneveOptions {
22 | opts, _ := ctx.Value(contextKeyGeneveOptions).(AwsGeneveOptions)
23 | return opts
24 | }
25 |
26 | func ContextWithAddrs(ctx context.Context, source, dest *net.TCPAddr) context.Context {
27 | slice := []*net.TCPAddr{source, dest}
28 | return context.WithValue(ctx, contextKeyAddrs, slice)
29 | }
30 |
31 | func AddrsFromContext(ctx context.Context) (source *net.TCPAddr, dest *net.TCPAddr) {
32 | slice := ctx.Value(contextKeyAddrs).([]*net.TCPAddr)
33 | return slice[0], slice[1]
34 | }
35 |
36 | func ContextWithNetstack(ctx context.Context, netstack *stack.Stack) context.Context {
37 | return context.WithValue(ctx, contextKeyNetstack, netstack)
38 | }
39 |
40 | func NetstackFromContext(ctx context.Context) *stack.Stack {
41 | return ctx.Value(contextKeyNetstack).(*stack.Stack)
42 | }
43 |
--------------------------------------------------------------------------------
/gwlb/default_handler.go:
--------------------------------------------------------------------------------
1 | package gwlb
2 |
3 | import (
4 | "crypto/tls"
5 | "net"
6 | "net/http"
7 | "net/http/httputil"
8 | "time"
9 | )
10 |
11 | func DefaultHandler(interceptor Interceptor, tlsClientConfig *tls.Config) http.Handler {
12 | rp := &httputil.ReverseProxy{
13 | ModifyResponse: interceptor.OnResponse,
14 | Director: interceptor.OnRequest,
15 | Transport: &http.Transport{
16 | DialContext: DialContext,
17 | // these are copied from stdlib default
18 | ForceAttemptHTTP2: true,
19 | MaxIdleConns: 100,
20 | IdleConnTimeout: 90 * time.Second,
21 | TLSHandshakeTimeout: 10 * time.Second,
22 | ExpectContinueTimeout: 1 * time.Second,
23 | TLSClientConfig: tlsClientConfig,
24 | },
25 | }
26 |
27 | h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
28 | ctx := r.Context()
29 |
30 | r.URL.Host = r.Host
31 | port := ctx.Value(http.LocalAddrContextKey).(*net.TCPAddr).Port
32 | if port == 443 {
33 | r.URL.Scheme = "https"
34 | } else if port == 80 {
35 | r.URL.Scheme = "http"
36 | }
37 |
38 | if r.Header.Get("Upgrade") == "websocket" {
39 | proxyWebsocket(w, r, tlsClientConfig)
40 | } else {
41 | rp.ServeHTTP(w, r)
42 | }
43 | })
44 |
45 | return h
46 | }
47 |
--------------------------------------------------------------------------------
/gwlb/dial.go:
--------------------------------------------------------------------------------
1 | package gwlb
2 |
3 | import (
4 | "context"
5 | "inet.af/netstack/tcpip"
6 | "inet.af/netstack/tcpip/adapters/gonet"
7 | "inet.af/netstack/tcpip/network/ipv4"
8 | "net"
9 | "net/http"
10 | )
11 |
12 | func DialContext(ctx context.Context, network, addr string) (net.Conn, error) {
13 | intendedAddr := ctx.Value(http.LocalAddrContextKey).(*net.TCPAddr)
14 | sourceAddr, _ := AddrsFromContext(ctx)
15 | netstack := NetstackFromContext(ctx)
16 |
17 | remote := tcpip.FullAddress{Addr: tcpip.Address(intendedAddr.IP.To4()), Port: uint16(intendedAddr.Port)}
18 | local := tcpip.FullAddress{Addr: tcpip.Address(sourceAddr.IP), Port: uint16(sourceAddr.Port)}
19 | return gonet.DialTCPWithBind(ctx, netstack, local, remote, ipv4.ProtocolNumber)
20 | }
21 |
--------------------------------------------------------------------------------
/gwlb/fastpath.go:
--------------------------------------------------------------------------------
1 | package gwlb
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "github.com/aidansteele/flowdog/gwlb/mirror"
7 | "net"
8 | "time"
9 | )
10 |
11 | func fastPath(ctx context.Context, ch chan genevePacket, gwlbConn *net.UDPConn, mirrorch chan mirror.Packet, timeout time.Duration) {
12 | for {
13 | select {
14 | case <-ctx.Done():
15 | return
16 | case pkt := <-ch:
17 | _, err := gwlbConn.Write(pkt.buf)
18 | if err != nil {
19 | fmt.Printf("%+v\n", err)
20 | panic(err)
21 | }
22 | mirrorch <- mirror.New(pkt.buf, true)
23 | case <-time.After(timeout):
24 | return
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/gwlb/geneve_options.go:
--------------------------------------------------------------------------------
1 | package gwlb
2 |
3 | import (
4 | "encoding/binary"
5 | "github.com/google/gopacket"
6 | "github.com/google/gopacket/layers"
7 | )
8 |
9 | const (
10 | AwsGeneveClass = 0x108
11 | AwsGeneveTypeVpcEndpointId = 0x1
12 | AwsGeneveTypeAttachmentId = 0x2
13 | AwsGeneveTypeFlowCookie = 0x3
14 | )
15 |
16 | type AwsGeneveOptions struct {
17 | VpcEndpointId uint64
18 | AttachmentId uint64
19 | FlowCookie uint32
20 | }
21 |
22 | func ExtractAwsGeneveOptions(pkt gopacket.Packet) AwsGeneveOptions {
23 | geneveLayer := pkt.Layer(layers.LayerTypeGeneve).(*layers.Geneve)
24 |
25 | opts := AwsGeneveOptions{}
26 |
27 | for _, option := range geneveLayer.Options {
28 | if option.Class != AwsGeneveClass {
29 | continue
30 | }
31 |
32 | switch option.Type {
33 | case AwsGeneveTypeVpcEndpointId:
34 | opts.VpcEndpointId = binary.BigEndian.Uint64(option.Data)
35 | case AwsGeneveTypeAttachmentId:
36 | opts.AttachmentId = binary.BigEndian.Uint64(option.Data)
37 | case AwsGeneveTypeFlowCookie:
38 | opts.FlowCookie = binary.BigEndian.Uint32(option.Data)
39 | default:
40 | // no-op
41 | }
42 | }
43 |
44 | return opts
45 | }
46 |
--------------------------------------------------------------------------------
/gwlb/interceptors.go:
--------------------------------------------------------------------------------
1 | package gwlb
2 |
3 | import "net/http"
4 |
5 | // Interceptor is modelled on https://pkg.go.dev/net/http/httputil#ReverseProxy
6 | type Interceptor interface {
7 | OnRequest(req *http.Request)
8 | OnResponse(resp *http.Response) error
9 | }
10 |
11 | type Chain []Interceptor
12 |
13 | func (c Chain) OnRequest(req *http.Request) {
14 | for _, interceptor := range c {
15 | interceptor.OnRequest(req)
16 | }
17 | }
18 |
19 | func (c Chain) OnResponse(resp *http.Response) error {
20 | for _, interceptor := range c {
21 | err := interceptor.OnResponse(resp)
22 | if err != nil {
23 | return err
24 | }
25 | }
26 |
27 | return nil
28 | }
29 |
--------------------------------------------------------------------------------
/gwlb/mirror/mirrored_packet.go:
--------------------------------------------------------------------------------
1 | package mirror
2 |
3 | type Type int
4 |
5 | const (
6 | TypeUnknown = Type(iota)
7 | TypePreRewrite
8 | TypePostRewrite
9 | )
10 |
11 | type Packet struct {
12 | Packet []byte
13 | Type Type
14 | }
15 |
16 | func New(pkt []byte, pre bool) Packet {
17 | mirrored := make([]byte, len(pkt))
18 | copy(mirrored, pkt)
19 |
20 | if pre {
21 | return Packet{Packet: mirrored, Type: TypePreRewrite}
22 | } else {
23 | return Packet{Packet: mirrored, Type: TypePostRewrite}
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/gwlb/serve.go:
--------------------------------------------------------------------------------
1 | package gwlb
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "github.com/aidansteele/flowdog/gwlb/mirror"
7 | "github.com/google/gopacket"
8 | "github.com/google/gopacket/layers"
9 | "github.com/pkg/errors"
10 | "io"
11 | "net"
12 | "net/http"
13 | )
14 |
15 | type genevePacket struct {
16 | buf []byte
17 | addr *net.UDPAddr
18 | pkt gopacket.Packet
19 | }
20 |
21 | type Server struct {
22 | Acceptor FlowAcceptor
23 | Handler http.Handler
24 | KeyLogWriter io.Writer
25 | Mirror chan mirror.Packet
26 | }
27 |
28 | func (s *Server) Serve(ctx context.Context, conn *net.UDPConn) error {
29 | flows := map[uint32]chan genevePacket{}
30 | flowEndedCh := make(chan uint32)
31 |
32 | pktbuf := make([]byte, 16_384)
33 |
34 | for {
35 | select {
36 | case <-ctx.Done():
37 | return ctx.Err()
38 | case flowCookie := <-flowEndedCh:
39 | fmt.Printf("deleting flow %08x\n", flowCookie)
40 | delete(flows, flowCookie)
41 | default:
42 | n, addr, err := conn.ReadFromUDP(pktbuf)
43 | if err != nil {
44 | return errors.WithStack(err)
45 | }
46 |
47 | newbuf := make([]byte, n)
48 | copy(newbuf, pktbuf[:n])
49 |
50 | pkt := gopacket.NewPacket(newbuf, layers.LayerTypeGeneve, gopacket.Default)
51 | opts := ExtractAwsGeneveOptions(pkt)
52 |
53 | cookie := opts.FlowCookie
54 | ch, found := flows[cookie]
55 | if !found {
56 | ch = make(chan genevePacket)
57 | flows[cookie] = ch
58 | go func() {
59 | newFlow(ctx, ch, opts, newFlowOptions{
60 | acceptor: s.Acceptor,
61 | handler: s.Handler,
62 | mirror: s.Mirror,
63 | keyLogger: s.KeyLogWriter,
64 | })
65 | flowEndedCh <- cookie
66 | }()
67 | }
68 |
69 | ch <- genevePacket{
70 | buf: newbuf,
71 | addr: addr,
72 | pkt: pkt,
73 | }
74 | }
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/gwlb/shark/filter.go:
--------------------------------------------------------------------------------
1 | package shark
2 |
3 | import (
4 | "github.com/google/gopacket/layers"
5 | "github.com/google/gopacket/pcap"
6 | "github.com/pkg/errors"
7 | )
8 |
9 | func FilterVM(input string) (*pcap.BPF, error) {
10 | vm, err := pcap.NewBPF(layers.LinkType(12), 0x10000, input)
11 | return vm, errors.WithStack(err)
12 | }
13 |
--------------------------------------------------------------------------------
/gwlb/shark/shark.go:
--------------------------------------------------------------------------------
1 | package shark
2 |
3 | import (
4 | "bufio"
5 | "bytes"
6 | "context"
7 | "fmt"
8 | "github.com/aidansteele/flowdog/gwlb/mirror"
9 | "github.com/google/gopacket"
10 | "github.com/google/gopacket/layers"
11 | "github.com/google/gopacket/pcap"
12 | "github.com/pkg/errors"
13 | "google.golang.org/grpc"
14 | "google.golang.org/protobuf/types/known/timestamppb"
15 | "io"
16 | "net"
17 | "sync"
18 | "time"
19 | )
20 |
21 | //go:generate protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative shark.proto
22 |
23 | type SharkServer struct {
24 | keyLogWriter io.Writer
25 | keyLogReader io.Reader
26 | ctx context.Context
27 | clients []*client
28 | mut sync.Mutex
29 | }
30 |
31 | func NewSharkServer() *SharkServer {
32 | pr, pw := io.Pipe()
33 |
34 | return &SharkServer{keyLogWriter: pw, keyLogReader: pr}
35 | }
36 |
37 | func (s *SharkServer) Serve(ctx context.Context, l net.Listener, ch chan mirror.Packet) error {
38 | s.ctx = ctx
39 | go s.run(ch)
40 |
41 | go s.streamSslKeyLog(ctx)
42 |
43 | gs := grpc.NewServer()
44 | RegisterVpcsharkServer(gs, s)
45 |
46 | go func() {
47 | <-ctx.Done()
48 | gs.Stop()
49 | }()
50 |
51 | err := gs.Serve(l)
52 | return errors.WithStack(err)
53 | }
54 |
55 | func (s *SharkServer) KeyLogWriter() io.Writer {
56 | return s.keyLogWriter
57 | }
58 |
59 | func (s *SharkServer) streamSslKeyLog(ctx context.Context) {
60 | scan := bufio.NewScanner(s.keyLogReader)
61 | buf := &bytes.Buffer{}
62 |
63 | for scan.Scan() {
64 | if ctx.Err() != nil {
65 | return
66 | }
67 |
68 | buf.Write(scan.Bytes())
69 | buf.WriteByte('\n')
70 | s.forEachClient(func(c *client) error {
71 | return c.stream.Send(&GetPacketsOutput{
72 | Time: timestamppb.New(time.Now()),
73 | SslKeyLog: buf.Bytes(),
74 | })
75 | })
76 | buf.Reset()
77 | }
78 | }
79 |
80 | func (s *SharkServer) forEachClient(fn func(c *client) error) {
81 | s.mut.Lock()
82 | defer s.mut.Unlock()
83 |
84 | validLen := 0
85 |
86 | for _, stream := range s.clients {
87 | err := fn(stream)
88 | if err == nil {
89 | s.clients[validLen] = stream
90 | validLen++
91 | }
92 | }
93 |
94 | // nil out invalid clients to garbage collect them
95 | for idx := validLen; idx < len(s.clients); idx++ {
96 | fmt.Println("removing an invalid shark listener")
97 | s.clients[idx] = nil
98 | }
99 | s.clients = s.clients[:validLen]
100 | }
101 |
102 | func (s *SharkServer) run(ch chan mirror.Packet) {
103 | for {
104 | select {
105 | case <-s.ctx.Done():
106 | return
107 | case pkt := <-ch:
108 | geneve := gopacket.NewPacket(pkt.Packet, layers.LayerTypeGeneve, gopacket.Default).Layer(layers.LayerTypeGeneve).(*layers.Geneve)
109 | s.forEachClient(func(c *client) error {
110 | if c.mirrorType != pkt.Type {
111 | return nil
112 | }
113 |
114 | match := c.vm.Matches(gopacket.CaptureInfo{}, geneve.LayerPayload())
115 | if !match {
116 | return nil
117 | }
118 |
119 | return c.stream.Send(&GetPacketsOutput{
120 | Time: timestamppb.New(time.Now()),
121 | Payload: pkt.Packet,
122 | })
123 | })
124 | }
125 | }
126 | }
127 |
128 | type client struct {
129 | stream Vpcshark_GetPacketsServer
130 | vm *pcap.BPF
131 | mirrorType mirror.Type
132 | }
133 |
134 | func (s *SharkServer) GetPackets(input *GetPacketsInput, stream Vpcshark_GetPacketsServer) error {
135 | vm, err := FilterVM(input.Filter)
136 | if err != nil {
137 | return err
138 | }
139 |
140 | mirrorType := mirror.TypeUnknown
141 | switch input.PacketType {
142 | case PacketType_PRE:
143 | mirrorType = mirror.TypePreRewrite
144 | case PacketType_POST:
145 | mirrorType = mirror.TypePostRewrite
146 | case PacketType_UNKNOWN:
147 | fallthrough
148 | default:
149 | return errors.New("unknown packet type")
150 | }
151 |
152 | s.mut.Lock()
153 | s.clients = append(s.clients, &client{stream: stream, vm: vm, mirrorType: mirrorType})
154 | s.mut.Unlock()
155 |
156 | <-s.ctx.Done()
157 | return nil
158 | }
159 |
160 | func (s *SharkServer) mustEmbedUnimplementedVpcsharkServer() {}
161 |
--------------------------------------------------------------------------------
/gwlb/shark/shark.pb.go:
--------------------------------------------------------------------------------
1 | // Code generated by protoc-gen-go. DO NOT EDIT.
2 | // versions:
3 | // protoc-gen-go v1.27.1
4 | // protoc v3.19.3
5 | // source: shark.proto
6 |
7 | package shark
8 |
9 | import (
10 | protoreflect "google.golang.org/protobuf/reflect/protoreflect"
11 | protoimpl "google.golang.org/protobuf/runtime/protoimpl"
12 | timestamppb "google.golang.org/protobuf/types/known/timestamppb"
13 | reflect "reflect"
14 | sync "sync"
15 | )
16 |
17 | const (
18 | // Verify that this generated code is sufficiently up-to-date.
19 | _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
20 | // Verify that runtime/protoimpl is sufficiently up-to-date.
21 | _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
22 | )
23 |
24 | type PacketType int32
25 |
26 | const (
27 | PacketType_UNKNOWN PacketType = 0
28 | PacketType_PRE PacketType = 1
29 | PacketType_POST PacketType = 2
30 | )
31 |
32 | // Enum value maps for PacketType.
33 | var (
34 | PacketType_name = map[int32]string{
35 | 0: "UNKNOWN",
36 | 1: "PRE",
37 | 2: "POST",
38 | }
39 | PacketType_value = map[string]int32{
40 | "UNKNOWN": 0,
41 | "PRE": 1,
42 | "POST": 2,
43 | }
44 | )
45 |
46 | func (x PacketType) Enum() *PacketType {
47 | p := new(PacketType)
48 | *p = x
49 | return p
50 | }
51 |
52 | func (x PacketType) String() string {
53 | return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
54 | }
55 |
56 | func (PacketType) Descriptor() protoreflect.EnumDescriptor {
57 | return file_shark_proto_enumTypes[0].Descriptor()
58 | }
59 |
60 | func (PacketType) Type() protoreflect.EnumType {
61 | return &file_shark_proto_enumTypes[0]
62 | }
63 |
64 | func (x PacketType) Number() protoreflect.EnumNumber {
65 | return protoreflect.EnumNumber(x)
66 | }
67 |
68 | // Deprecated: Use PacketType.Descriptor instead.
69 | func (PacketType) EnumDescriptor() ([]byte, []int) {
70 | return file_shark_proto_rawDescGZIP(), []int{0}
71 | }
72 |
73 | type GetPacketsInput struct {
74 | state protoimpl.MessageState
75 | sizeCache protoimpl.SizeCache
76 | unknownFields protoimpl.UnknownFields
77 |
78 | Filter string `protobuf:"bytes,1,opt,name=Filter,proto3" json:"Filter,omitempty"`
79 | PacketType PacketType `protobuf:"varint,2,opt,name=PacketType,proto3,enum=PacketType" json:"PacketType,omitempty"`
80 | }
81 |
82 | func (x *GetPacketsInput) Reset() {
83 | *x = GetPacketsInput{}
84 | if protoimpl.UnsafeEnabled {
85 | mi := &file_shark_proto_msgTypes[0]
86 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
87 | ms.StoreMessageInfo(mi)
88 | }
89 | }
90 |
91 | func (x *GetPacketsInput) String() string {
92 | return protoimpl.X.MessageStringOf(x)
93 | }
94 |
95 | func (*GetPacketsInput) ProtoMessage() {}
96 |
97 | func (x *GetPacketsInput) ProtoReflect() protoreflect.Message {
98 | mi := &file_shark_proto_msgTypes[0]
99 | if protoimpl.UnsafeEnabled && x != nil {
100 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
101 | if ms.LoadMessageInfo() == nil {
102 | ms.StoreMessageInfo(mi)
103 | }
104 | return ms
105 | }
106 | return mi.MessageOf(x)
107 | }
108 |
109 | // Deprecated: Use GetPacketsInput.ProtoReflect.Descriptor instead.
110 | func (*GetPacketsInput) Descriptor() ([]byte, []int) {
111 | return file_shark_proto_rawDescGZIP(), []int{0}
112 | }
113 |
114 | func (x *GetPacketsInput) GetFilter() string {
115 | if x != nil {
116 | return x.Filter
117 | }
118 | return ""
119 | }
120 |
121 | func (x *GetPacketsInput) GetPacketType() PacketType {
122 | if x != nil {
123 | return x.PacketType
124 | }
125 | return PacketType_UNKNOWN
126 | }
127 |
128 | type GetPacketsOutput struct {
129 | state protoimpl.MessageState
130 | sizeCache protoimpl.SizeCache
131 | unknownFields protoimpl.UnknownFields
132 |
133 | Payload []byte `protobuf:"bytes,1,opt,name=Payload,proto3" json:"Payload,omitempty"`
134 | Time *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=Time,proto3" json:"Time,omitempty"`
135 | SslKeyLog []byte `protobuf:"bytes,3,opt,name=SslKeyLog,proto3" json:"SslKeyLog,omitempty"`
136 | }
137 |
138 | func (x *GetPacketsOutput) Reset() {
139 | *x = GetPacketsOutput{}
140 | if protoimpl.UnsafeEnabled {
141 | mi := &file_shark_proto_msgTypes[1]
142 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
143 | ms.StoreMessageInfo(mi)
144 | }
145 | }
146 |
147 | func (x *GetPacketsOutput) String() string {
148 | return protoimpl.X.MessageStringOf(x)
149 | }
150 |
151 | func (*GetPacketsOutput) ProtoMessage() {}
152 |
153 | func (x *GetPacketsOutput) ProtoReflect() protoreflect.Message {
154 | mi := &file_shark_proto_msgTypes[1]
155 | if protoimpl.UnsafeEnabled && x != nil {
156 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
157 | if ms.LoadMessageInfo() == nil {
158 | ms.StoreMessageInfo(mi)
159 | }
160 | return ms
161 | }
162 | return mi.MessageOf(x)
163 | }
164 |
165 | // Deprecated: Use GetPacketsOutput.ProtoReflect.Descriptor instead.
166 | func (*GetPacketsOutput) Descriptor() ([]byte, []int) {
167 | return file_shark_proto_rawDescGZIP(), []int{1}
168 | }
169 |
170 | func (x *GetPacketsOutput) GetPayload() []byte {
171 | if x != nil {
172 | return x.Payload
173 | }
174 | return nil
175 | }
176 |
177 | func (x *GetPacketsOutput) GetTime() *timestamppb.Timestamp {
178 | if x != nil {
179 | return x.Time
180 | }
181 | return nil
182 | }
183 |
184 | func (x *GetPacketsOutput) GetSslKeyLog() []byte {
185 | if x != nil {
186 | return x.SslKeyLog
187 | }
188 | return nil
189 | }
190 |
191 | var File_shark_proto protoreflect.FileDescriptor
192 |
193 | var file_shark_proto_rawDesc = []byte{
194 | 0x0a, 0x0b, 0x73, 0x68, 0x61, 0x72, 0x6b, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1f, 0x67,
195 | 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74,
196 | 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x56,
197 | 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x73, 0x49, 0x6e, 0x70, 0x75,
198 | 0x74, 0x12, 0x16, 0x0a, 0x06, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28,
199 | 0x09, 0x52, 0x06, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x12, 0x2b, 0x0a, 0x0a, 0x50, 0x61, 0x63,
200 | 0x6b, 0x65, 0x74, 0x54, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x0b, 0x2e,
201 | 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0a, 0x50, 0x61, 0x63, 0x6b,
202 | 0x65, 0x74, 0x54, 0x79, 0x70, 0x65, 0x22, 0x7a, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x50, 0x61, 0x63,
203 | 0x6b, 0x65, 0x74, 0x73, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x50, 0x61,
204 | 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x50, 0x61, 0x79,
205 | 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x2e, 0x0a, 0x04, 0x54, 0x69, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01,
206 | 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74,
207 | 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x04,
208 | 0x54, 0x69, 0x6d, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x53, 0x73, 0x6c, 0x4b, 0x65, 0x79, 0x4c, 0x6f,
209 | 0x67, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x53, 0x73, 0x6c, 0x4b, 0x65, 0x79, 0x4c,
210 | 0x6f, 0x67, 0x2a, 0x2c, 0x0a, 0x0a, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x54, 0x79, 0x70, 0x65,
211 | 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x07, 0x0a,
212 | 0x03, 0x50, 0x52, 0x45, 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, 0x50, 0x4f, 0x53, 0x54, 0x10, 0x02,
213 | 0x32, 0x41, 0x0a, 0x08, 0x56, 0x70, 0x63, 0x73, 0x68, 0x61, 0x72, 0x6b, 0x12, 0x35, 0x0a, 0x0a,
214 | 0x47, 0x65, 0x74, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x73, 0x12, 0x10, 0x2e, 0x47, 0x65, 0x74,
215 | 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x73, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x1a, 0x11, 0x2e, 0x47,
216 | 0x65, 0x74, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x73, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x22,
217 | 0x00, 0x30, 0x01, 0x42, 0x2b, 0x5a, 0x29, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f,
218 | 0x6d, 0x2f, 0x61, 0x69, 0x64, 0x61, 0x6e, 0x73, 0x74, 0x65, 0x65, 0x6c, 0x65, 0x2f, 0x66, 0x6c,
219 | 0x6f, 0x77, 0x64, 0x6f, 0x67, 0x2f, 0x67, 0x77, 0x6c, 0x62, 0x2f, 0x73, 0x68, 0x61, 0x72, 0x6b,
220 | 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
221 | }
222 |
223 | var (
224 | file_shark_proto_rawDescOnce sync.Once
225 | file_shark_proto_rawDescData = file_shark_proto_rawDesc
226 | )
227 |
228 | func file_shark_proto_rawDescGZIP() []byte {
229 | file_shark_proto_rawDescOnce.Do(func() {
230 | file_shark_proto_rawDescData = protoimpl.X.CompressGZIP(file_shark_proto_rawDescData)
231 | })
232 | return file_shark_proto_rawDescData
233 | }
234 |
235 | var file_shark_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
236 | var file_shark_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
237 | var file_shark_proto_goTypes = []interface{}{
238 | (PacketType)(0), // 0: PacketType
239 | (*GetPacketsInput)(nil), // 1: GetPacketsInput
240 | (*GetPacketsOutput)(nil), // 2: GetPacketsOutput
241 | (*timestamppb.Timestamp)(nil), // 3: google.protobuf.Timestamp
242 | }
243 | var file_shark_proto_depIdxs = []int32{
244 | 0, // 0: GetPacketsInput.PacketType:type_name -> PacketType
245 | 3, // 1: GetPacketsOutput.Time:type_name -> google.protobuf.Timestamp
246 | 1, // 2: Vpcshark.GetPackets:input_type -> GetPacketsInput
247 | 2, // 3: Vpcshark.GetPackets:output_type -> GetPacketsOutput
248 | 3, // [3:4] is the sub-list for method output_type
249 | 2, // [2:3] is the sub-list for method input_type
250 | 2, // [2:2] is the sub-list for extension type_name
251 | 2, // [2:2] is the sub-list for extension extendee
252 | 0, // [0:2] is the sub-list for field type_name
253 | }
254 |
255 | func init() { file_shark_proto_init() }
256 | func file_shark_proto_init() {
257 | if File_shark_proto != nil {
258 | return
259 | }
260 | if !protoimpl.UnsafeEnabled {
261 | file_shark_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
262 | switch v := v.(*GetPacketsInput); i {
263 | case 0:
264 | return &v.state
265 | case 1:
266 | return &v.sizeCache
267 | case 2:
268 | return &v.unknownFields
269 | default:
270 | return nil
271 | }
272 | }
273 | file_shark_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
274 | switch v := v.(*GetPacketsOutput); i {
275 | case 0:
276 | return &v.state
277 | case 1:
278 | return &v.sizeCache
279 | case 2:
280 | return &v.unknownFields
281 | default:
282 | return nil
283 | }
284 | }
285 | }
286 | type x struct{}
287 | out := protoimpl.TypeBuilder{
288 | File: protoimpl.DescBuilder{
289 | GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
290 | RawDescriptor: file_shark_proto_rawDesc,
291 | NumEnums: 1,
292 | NumMessages: 2,
293 | NumExtensions: 0,
294 | NumServices: 1,
295 | },
296 | GoTypes: file_shark_proto_goTypes,
297 | DependencyIndexes: file_shark_proto_depIdxs,
298 | EnumInfos: file_shark_proto_enumTypes,
299 | MessageInfos: file_shark_proto_msgTypes,
300 | }.Build()
301 | File_shark_proto = out.File
302 | file_shark_proto_rawDesc = nil
303 | file_shark_proto_goTypes = nil
304 | file_shark_proto_depIdxs = nil
305 | }
306 |
--------------------------------------------------------------------------------
/gwlb/shark/shark.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";
2 |
3 | option go_package = "github.com/aidansteele/flowdog/gwlb/shark";
4 |
5 | import "google/protobuf/timestamp.proto";
6 |
7 | enum PacketType {
8 | UNKNOWN = 0;
9 | PRE = 1;
10 | POST = 2;
11 | }
12 |
13 | message GetPacketsInput {
14 | string Filter = 1;
15 | PacketType PacketType = 2;
16 | }
17 |
18 | message GetPacketsOutput {
19 | bytes Payload = 1;
20 | google.protobuf.Timestamp Time = 2;
21 | bytes SslKeyLog = 3;
22 | }
23 |
24 | service Vpcshark {
25 | rpc GetPackets(GetPacketsInput) returns (stream GetPacketsOutput) {}
26 | }
27 |
--------------------------------------------------------------------------------
/gwlb/shark/shark_grpc.pb.go:
--------------------------------------------------------------------------------
1 | // Code generated by protoc-gen-go-grpc. DO NOT EDIT.
2 | // versions:
3 | // - protoc-gen-go-grpc v1.2.0
4 | // - protoc v3.19.3
5 | // source: shark.proto
6 |
7 | package shark
8 |
9 | import (
10 | context "context"
11 | grpc "google.golang.org/grpc"
12 | codes "google.golang.org/grpc/codes"
13 | status "google.golang.org/grpc/status"
14 | )
15 |
16 | // This is a compile-time assertion to ensure that this generated file
17 | // is compatible with the grpc package it is being compiled against.
18 | // Requires gRPC-Go v1.32.0 or later.
19 | const _ = grpc.SupportPackageIsVersion7
20 |
21 | // VpcsharkClient is the client API for Vpcshark service.
22 | //
23 | // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
24 | type VpcsharkClient interface {
25 | GetPackets(ctx context.Context, in *GetPacketsInput, opts ...grpc.CallOption) (Vpcshark_GetPacketsClient, error)
26 | }
27 |
28 | type vpcsharkClient struct {
29 | cc grpc.ClientConnInterface
30 | }
31 |
32 | func NewVpcsharkClient(cc grpc.ClientConnInterface) VpcsharkClient {
33 | return &vpcsharkClient{cc}
34 | }
35 |
36 | func (c *vpcsharkClient) GetPackets(ctx context.Context, in *GetPacketsInput, opts ...grpc.CallOption) (Vpcshark_GetPacketsClient, error) {
37 | stream, err := c.cc.NewStream(ctx, &Vpcshark_ServiceDesc.Streams[0], "/Vpcshark/GetPackets", opts...)
38 | if err != nil {
39 | return nil, err
40 | }
41 | x := &vpcsharkGetPacketsClient{stream}
42 | if err := x.ClientStream.SendMsg(in); err != nil {
43 | return nil, err
44 | }
45 | if err := x.ClientStream.CloseSend(); err != nil {
46 | return nil, err
47 | }
48 | return x, nil
49 | }
50 |
51 | type Vpcshark_GetPacketsClient interface {
52 | Recv() (*GetPacketsOutput, error)
53 | grpc.ClientStream
54 | }
55 |
56 | type vpcsharkGetPacketsClient struct {
57 | grpc.ClientStream
58 | }
59 |
60 | func (x *vpcsharkGetPacketsClient) Recv() (*GetPacketsOutput, error) {
61 | m := new(GetPacketsOutput)
62 | if err := x.ClientStream.RecvMsg(m); err != nil {
63 | return nil, err
64 | }
65 | return m, nil
66 | }
67 |
68 | // VpcsharkServer is the server API for Vpcshark service.
69 | // All implementations must embed UnimplementedVpcsharkServer
70 | // for forward compatibility
71 | type VpcsharkServer interface {
72 | GetPackets(*GetPacketsInput, Vpcshark_GetPacketsServer) error
73 | mustEmbedUnimplementedVpcsharkServer()
74 | }
75 |
76 | // UnimplementedVpcsharkServer must be embedded to have forward compatible implementations.
77 | type UnimplementedVpcsharkServer struct {
78 | }
79 |
80 | func (UnimplementedVpcsharkServer) GetPackets(*GetPacketsInput, Vpcshark_GetPacketsServer) error {
81 | return status.Errorf(codes.Unimplemented, "method GetPackets not implemented")
82 | }
83 | func (UnimplementedVpcsharkServer) mustEmbedUnimplementedVpcsharkServer() {}
84 |
85 | // UnsafeVpcsharkServer may be embedded to opt out of forward compatibility for this service.
86 | // Use of this interface is not recommended, as added methods to VpcsharkServer will
87 | // result in compilation errors.
88 | type UnsafeVpcsharkServer interface {
89 | mustEmbedUnimplementedVpcsharkServer()
90 | }
91 |
92 | func RegisterVpcsharkServer(s grpc.ServiceRegistrar, srv VpcsharkServer) {
93 | s.RegisterService(&Vpcshark_ServiceDesc, srv)
94 | }
95 |
96 | func _Vpcshark_GetPackets_Handler(srv interface{}, stream grpc.ServerStream) error {
97 | m := new(GetPacketsInput)
98 | if err := stream.RecvMsg(m); err != nil {
99 | return err
100 | }
101 | return srv.(VpcsharkServer).GetPackets(m, &vpcsharkGetPacketsServer{stream})
102 | }
103 |
104 | type Vpcshark_GetPacketsServer interface {
105 | Send(*GetPacketsOutput) error
106 | grpc.ServerStream
107 | }
108 |
109 | type vpcsharkGetPacketsServer struct {
110 | grpc.ServerStream
111 | }
112 |
113 | func (x *vpcsharkGetPacketsServer) Send(m *GetPacketsOutput) error {
114 | return x.ServerStream.SendMsg(m)
115 | }
116 |
117 | // Vpcshark_ServiceDesc is the grpc.ServiceDesc for Vpcshark service.
118 | // It's only intended for direct use with grpc.RegisterService,
119 | // and not to be introspected or modified (even as a copy)
120 | var Vpcshark_ServiceDesc = grpc.ServiceDesc{
121 | ServiceName: "Vpcshark",
122 | HandlerType: (*VpcsharkServer)(nil),
123 | Methods: []grpc.MethodDesc{},
124 | Streams: []grpc.StreamDesc{
125 | {
126 | StreamName: "GetPackets",
127 | Handler: _Vpcshark_GetPackets_Handler,
128 | ServerStreams: true,
129 | },
130 | },
131 | Metadata: "shark.proto",
132 | }
133 |
--------------------------------------------------------------------------------
/gwlb/udpconn_map.go:
--------------------------------------------------------------------------------
1 | package gwlb
2 |
3 | import (
4 | "fmt"
5 | "github.com/tidwall/spinlock"
6 | "net"
7 | )
8 |
9 | var udpLock spinlock.Locker
10 | var udpMap map[int]*net.UDPConn
11 |
12 | func getUdpConn(remote *net.UDPAddr) *net.UDPConn {
13 | udpLock.Lock()
14 | defer udpLock.Unlock()
15 |
16 | lport := remote.Port
17 | if udpMap == nil {
18 | udpMap = map[int]*net.UDPConn{}
19 | }
20 |
21 | gwlbConn := udpMap[lport]
22 | if gwlbConn != nil {
23 | fmt.Println("reusing udp conn")
24 | return gwlbConn
25 | }
26 |
27 | // TODO: gwlb will have a different IP per AZ, right? is hard-coding it here a problem?
28 | raddr := &net.UDPAddr{IP: remote.IP, Port: 6081}
29 | laddr := &net.UDPAddr{Port: lport}
30 |
31 | var err error
32 | gwlbConn, err = net.DialUDP("udp", laddr, raddr)
33 | if err != nil {
34 | fmt.Printf("%+v\n", err)
35 | panic(err)
36 | }
37 |
38 | udpMap[lport] = gwlbConn
39 | return gwlbConn
40 | }
41 |
--------------------------------------------------------------------------------
/gwlb/websocket.go:
--------------------------------------------------------------------------------
1 | package gwlb
2 |
3 | import (
4 | "context"
5 | "crypto/tls"
6 | "fmt"
7 | "github.com/gorilla/websocket"
8 | "github.com/pkg/errors"
9 | "golang.org/x/sync/errgroup"
10 | "net"
11 | "net/http"
12 | )
13 |
14 | func proxyWebsocket(w http.ResponseWriter, r *http.Request, tlsClientConfig *tls.Config) {
15 | up := websocket.Upgrader{CheckOrigin: nil} // TODO: check this field
16 |
17 | ctx := r.Context()
18 | port := ctx.Value(http.LocalAddrContextKey).(*net.TCPAddr).Port
19 | if port == 443 {
20 | r.URL.Scheme = "wss"
21 | } else if port == 80 {
22 | r.URL.Scheme = "ws"
23 | }
24 |
25 | newHeaders := r.Header.Clone()
26 | newHeaders.Del("Upgrade")
27 | newHeaders.Del("Connection")
28 | newHeaders.Del("Sec-Websocket-Key")
29 | newHeaders.Del("Sec-Websocket-Version")
30 | newHeaders.Del("Sec-Websocket-Extensions")
31 | newHeaders.Del("Sec-Websocket-Protocol")
32 |
33 | dialer := &websocket.Dialer{
34 | NetDialContext: DialContext,
35 | TLSClientConfig: tlsClientConfig,
36 | }
37 |
38 | serverConn, resp, err := dialer.DialContext(ctx, r.URL.String(), newHeaders)
39 | if err != nil {
40 | fmt.Printf("wss dial %+v\n", err)
41 | panic(err)
42 | }
43 |
44 | clientConn, err := up.Upgrade(w, r, resp.Header)
45 | if err != nil {
46 | fmt.Printf("%+v\n", err)
47 | panic(err)
48 | }
49 |
50 | g, ctx := errgroup.WithContext(ctx)
51 |
52 | g.Go(func() error {
53 | return wsPumpLoop(ctx, serverConn, clientConn)
54 | })
55 |
56 | g.Go(func() error {
57 | return wsPumpLoop(ctx, clientConn, serverConn)
58 | })
59 |
60 | err = g.Wait()
61 | if err != nil {
62 | fmt.Printf("%+v\n", err)
63 | panic(err)
64 | }
65 | }
66 |
67 | func wsPumpLoop(ctx context.Context, a, b *websocket.Conn) error {
68 | for {
69 | select {
70 | case <-ctx.Done():
71 | return ctx.Err()
72 | default:
73 | mt, p, err := a.ReadMessage()
74 | if err != nil {
75 | return errors.WithStack(err)
76 | }
77 |
78 | err = b.WriteMessage(mt, p)
79 | if err != nil {
80 | return errors.WithStack(err)
81 | }
82 | }
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/kmssigner/generate/generate.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "crypto/rand"
5 | "crypto/x509"
6 | "crypto/x509/pkix"
7 | "encoding/pem"
8 | "fmt"
9 | "github.com/aidansteele/flowdog/kmssigner"
10 | "github.com/aws/aws-sdk-go/aws/session"
11 | "github.com/aws/aws-sdk-go/service/kms"
12 | "math/big"
13 | "os"
14 | "time"
15 | )
16 |
17 | func main() {
18 | keyId := os.Args[1]
19 |
20 | sess, err := session.NewSessionWithOptions(session.Options{SharedConfigState: session.SharedConfigEnable, Profile: "ge"})
21 | if err != nil {
22 | fmt.Printf("%+v\n", err)
23 | panic(err)
24 | }
25 |
26 | tmpl := x509.Certificate{
27 | Subject: pkix.Name{CommonName: "Aidan KMS TLS Co."},
28 | SerialNumber: big.NewInt(1),
29 | NotBefore: time.Now(),
30 | NotAfter: time.Now().AddDate(10, 0, 0),
31 | KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
32 | ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
33 | BasicConstraintsValid: true,
34 | IsCA: true,
35 | }
36 |
37 | signer, err := kmssigner.New(kms.New(sess), keyId)
38 | if err != nil {
39 | fmt.Printf("%+v\n", err)
40 | panic(err)
41 | }
42 |
43 | der, err := x509.CreateCertificate(rand.Reader, &tmpl, &tmpl, signer.Public(), signer)
44 | if err != nil {
45 | fmt.Printf("%+v\n", err)
46 | panic(err)
47 | }
48 |
49 | pemBytes := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: der})
50 | fmt.Println(string(pemBytes))
51 | }
52 |
--------------------------------------------------------------------------------
/kmssigner/kmssigner.go:
--------------------------------------------------------------------------------
1 | package kmssigner
2 |
3 | import (
4 | "crypto"
5 | "crypto/x509"
6 | "github.com/aws/aws-sdk-go/aws"
7 | "github.com/aws/aws-sdk-go/service/kms"
8 | "github.com/aws/aws-sdk-go/service/kms/kmsiface"
9 | "github.com/pkg/errors"
10 | "io"
11 | )
12 |
13 | var signatureMap = map[crypto.Hash]string{
14 | crypto.SHA256: kms.SigningAlgorithmSpecEcdsaSha256,
15 | crypto.SHA384: kms.SigningAlgorithmSpecEcdsaSha384,
16 | crypto.SHA512: kms.SigningAlgorithmSpecEcdsaSha512,
17 | }
18 |
19 | type signer struct {
20 | api kmsiface.KMSAPI
21 | keyId string
22 | pub crypto.PublicKey
23 | }
24 |
25 | func New(api kmsiface.KMSAPI, keyId string) (crypto.Signer, error) {
26 | getPub, err := api.GetPublicKey(&kms.GetPublicKeyInput{KeyId: &keyId})
27 | if err != nil {
28 | return nil, errors.WithStack(err)
29 | }
30 |
31 | pub, err := x509.ParsePKIXPublicKey(getPub.PublicKey)
32 | if err != nil {
33 | return nil, errors.WithStack(err)
34 | }
35 |
36 | return &signer{api: api, keyId: keyId, pub: pub}, nil
37 | }
38 |
39 | func (k *signer) Public() crypto.PublicKey {
40 | return k.pub
41 | }
42 |
43 | func (k *signer) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) (signature []byte, err error) {
44 | sign, err := k.api.Sign(&kms.SignInput{
45 | KeyId: &k.keyId,
46 | Message: digest,
47 | MessageType: aws.String(kms.MessageTypeDigest),
48 | SigningAlgorithm: aws.String(signatureMap[opts.HashFunc()]),
49 | })
50 | if err != nil {
51 | return nil, errors.WithStack(err)
52 | }
53 |
54 | return sign.Signature, nil
55 | }
56 |
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "crypto/tls"
6 | "crypto/x509"
7 | "encoding/pem"
8 | "fmt"
9 | "github.com/aidansteele/flowdog/examples/account_id_emf"
10 | "github.com/aidansteele/flowdog/examples/geneve_headers"
11 | "github.com/aidansteele/flowdog/examples/lambda_acceptor"
12 | "github.com/aidansteele/flowdog/examples/sts_rickroll"
13 | "github.com/aidansteele/flowdog/examples/upsidedown"
14 | "github.com/aidansteele/flowdog/examples/webfirehose"
15 | "github.com/aidansteele/flowdog/gwlb"
16 | "github.com/aidansteele/flowdog/gwlb/mirror"
17 | "github.com/aidansteele/flowdog/gwlb/shark"
18 | "github.com/aidansteele/flowdog/kmssigner"
19 | "github.com/aidansteele/flowdog/mytls"
20 | "github.com/aws/aws-sdk-go/aws/session"
21 | "github.com/aws/aws-sdk-go/service/ec2"
22 | "github.com/aws/aws-sdk-go/service/firehose"
23 | "github.com/aws/aws-sdk-go/service/kms"
24 | "github.com/aws/aws-sdk-go/service/kms/kmsiface"
25 | "github.com/aws/aws-sdk-go/service/lambda"
26 | _ "github.com/google/gopacket/layers"
27 | "github.com/pkg/errors"
28 | "net"
29 | "net/http"
30 | "os"
31 | "time"
32 | )
33 |
34 | func main() {
35 | ctx := context.Background()
36 |
37 | sess, err := session.NewSessionWithOptions(session.Options{
38 | SharedConfigState: session.SharedConfigEnable,
39 | })
40 | if err != nil {
41 | fmt.Printf("%+v\n", err)
42 | panic(err)
43 | }
44 |
45 | err = setupTls(kms.New(sess))
46 | if err != nil {
47 | fmt.Printf("%+v\n", err)
48 | panic(err)
49 | }
50 |
51 | conn, err := net.ListenUDP("udp", &net.UDPAddr{Port: 6081})
52 | if err != nil {
53 | panic(err)
54 | }
55 |
56 | wf := webfirehose.New(firehose.New(sess), ec2.New(sess), "webfirehose-Firehose-0k2UpKEJfMz0")
57 | go func() {
58 | err := wf.Run(ctx)
59 | if err != nil {
60 | fmt.Printf("%+v\n", err)
61 | panic(err)
62 | }
63 | }()
64 |
65 | chain := gwlb.Chain{
66 | wf,
67 | &geneve_headers.GeneveHeaders{},
68 | &account_id_emf.AccountIdEmf{},
69 | &sts_rickroll.StsRickroll{},
70 | upsidedown.UpsideDown(),
71 | //cloudfront_functions.NewRickroll(),
72 | }
73 |
74 | acceptor, err := lambda_acceptor.New(
75 | lambda.New(sess),
76 | os.Getenv("LAMBDA_ACCEPTOR_ARN"),
77 | ec2.New(sess),
78 | )
79 | if err != nil {
80 | fmt.Printf("%+v\n", err)
81 | panic(err)
82 | }
83 |
84 | ctx := context.Background()
85 |
86 | shark := shark.NewSharkServer()
87 | sharkL, err := net.Listen("tcp", "127.0.0.1:7081")
88 | if err != nil {
89 | fmt.Printf("%+v\n", err)
90 | panic(err)
91 | }
92 |
93 | mirrorCh := make(chan mirror.Packet)
94 | go shark.Serve(ctx, sharkL, mirrorCh)
95 |
96 | server := &gwlb.Server{
97 | Handler: gwlb.DefaultHandler(chain, &tls.Config{KeyLogWriter: shark.KeyLogWriter()}),
98 | Acceptor: acceptor,
99 | KeyLogWriter: shark.KeyLogWriter(),
100 | Mirror: mirrorCh,
101 | }
102 |
103 | go healthChecks()
104 |
105 | err = server.Serve(ctx, conn)
106 | if err != nil {
107 | fmt.Printf("%+v\n", err)
108 | panic(err)
109 | }
110 | }
111 |
112 | func setupTls(kmsapi kmsiface.KMSAPI) error {
113 | // TODO: env var or something
114 | certBlock, _ := pem.Decode([]byte(`
115 | -----BEGIN CERTIFICATE-----
116 | MIIBfjCCASSgAwIBAgIBATAKBggqhkjOPQQDAjAcMRowGAYDVQQDExFBaWRhbiBL
117 | TVMgVExTIENvLjAeFw0yMjAxMTEwMzU5NTBaFw0zMjAxMTEwMzU5NTBaMBwxGjAY
118 | BgNVBAMTEUFpZGFuIEtNUyBUTFMgQ28uMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcD
119 | QgAE7nApN4JApU+uocRZiefgFoHwpeTTHZkcqVUUTfJp67GmmV4WJ2MTXAH2W+MP
120 | 2M8d5cHmgcrSAi6vIzLjvRZBuKNXMFUwDgYDVR0PAQH/BAQDAgKEMBMGA1UdJQQM
121 | MAoGCCsGAQUFBwMBMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFIAvKHJ7WiTA
122 | vlpCx+VMI/+lwgfuMAoGCCqGSM49BAMCA0gAMEUCIFwVxZTneQupaLH2Cunk7FdE
123 | nca45vDEVkjEZw7ERb7SAiEA5Sv3PbIBQSGihtWG4SOJ4tm8US29wM81w9Okl0vR
124 | qpw=
125 | -----END CERTIFICATE-----
126 | `))
127 |
128 | cert, err := x509.ParseCertificate(certBlock.Bytes)
129 | if err != nil {
130 | return errors.WithStack(err)
131 | }
132 |
133 | signer, err := kmssigner.New(kmsapi, os.Getenv("KEY_ID"))
134 | if err != nil {
135 | return errors.WithStack(err)
136 | }
137 |
138 | return mytls.SetIntermediateCA(
139 | os.Getenv("INTERMEDIATE_CA_NAME"),
140 | time.Now().AddDate(1, 0, 0),
141 | cert,
142 | signer,
143 | )
144 | }
145 |
146 | func healthChecks() {
147 | http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
148 | fmt.Println("health checks")
149 | w.Write([]byte("ok"))
150 | })
151 |
152 | err := http.ListenAndServe(":8080", nil)
153 | if err != nil {
154 | fmt.Printf("%+v\n", err)
155 | panic(err)
156 | }
157 | }
158 |
--------------------------------------------------------------------------------
/mytls/intermediate.go:
--------------------------------------------------------------------------------
1 | package mytls
2 |
3 | import (
4 | "crypto"
5 | "crypto/ecdsa"
6 | "crypto/elliptic"
7 | "crypto/rand"
8 | "crypto/x509"
9 | "crypto/x509/pkix"
10 | "github.com/pkg/errors"
11 | "math/big"
12 | "time"
13 | )
14 |
15 | var (
16 | intermediateCert *x509.Certificate
17 | intermediateSigner crypto.Signer
18 | )
19 |
20 | func SetIntermediateCA(commonName string, expiry time.Time, caCertificate *x509.Certificate, signer crypto.Signer) error {
21 | limit := (&big.Int{}).Lsh(big.NewInt(1), 128)
22 | serialNumber, err := rand.Int(rand.Reader, limit)
23 | if err != nil {
24 | return errors.WithStack(err)
25 | }
26 |
27 | tmpl := x509.Certificate{
28 | Subject: pkix.Name{CommonName: commonName},
29 | SerialNumber: serialNumber,
30 | NotBefore: time.Now(),
31 | NotAfter: expiry,
32 | KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
33 | ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
34 | BasicConstraintsValid: true,
35 | IsCA: true,
36 | }
37 |
38 | intermediateSigner, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
39 | if err != nil {
40 | return errors.WithStack(err)
41 | }
42 |
43 | derBytes, err := x509.CreateCertificate(rand.Reader, &tmpl, caCertificate, intermediateSigner.Public(), signer)
44 | if err != nil {
45 | return errors.WithStack(err)
46 | }
47 |
48 | intermediateCert, err = x509.ParseCertificate(derBytes)
49 | if err != nil {
50 | return errors.WithStack(err)
51 | }
52 |
53 | return nil
54 | }
55 |
--------------------------------------------------------------------------------
/mytls/tls_config.go:
--------------------------------------------------------------------------------
1 | package mytls
2 |
3 | import (
4 | "crypto/ecdsa"
5 | "crypto/elliptic"
6 | "crypto/rand"
7 | "crypto/tls"
8 | "crypto/x509"
9 | "crypto/x509/pkix"
10 | "github.com/pkg/errors"
11 | "math/big"
12 | "sync"
13 | "time"
14 | )
15 |
16 | var certificateCache map[string]*tls.Certificate
17 | var certificateCacheLock sync.Mutex
18 |
19 | func TlsConfig() *tls.Config {
20 | return &tls.Config{
21 | GetCertificate: func(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
22 | certificateCacheLock.Lock()
23 | defer certificateCacheLock.Unlock()
24 |
25 | domain := info.ServerName
26 |
27 | if certificateCache == nil {
28 | certificateCache = map[string]*tls.Certificate{}
29 | }
30 |
31 | cert := certificateCache[domain]
32 | if cert != nil {
33 | return cert, nil
34 | }
35 |
36 | cert, err := generateTlsCertificate(domain)
37 | certificateCache[domain] = cert
38 | return cert, err
39 | },
40 | }
41 | }
42 |
43 | func generateTlsCertificate(domain string) (*tls.Certificate, error) {
44 | limit := (&big.Int{}).Lsh(big.NewInt(1), 128)
45 | serialNumber, _ := rand.Int(rand.Reader, limit)
46 |
47 | tmpl := x509.Certificate{
48 | Subject: pkix.Name{CommonName: domain},
49 | DNSNames: []string{domain},
50 | SerialNumber: serialNumber,
51 | NotBefore: time.Now(),
52 | NotAfter: time.Now().AddDate(1, 0, 0),
53 | KeyUsage: x509.KeyUsageDigitalSignature,
54 | ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
55 | BasicConstraintsValid: true,
56 | }
57 |
58 | key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
59 | if err != nil {
60 | return nil, errors.WithStack(err)
61 | }
62 |
63 | derBytes, err := x509.CreateCertificate(rand.Reader, &tmpl, intermediateCert, key.Public(), intermediateSigner)
64 | if err != nil {
65 | return nil, errors.WithStack(err)
66 | }
67 |
68 | return &tls.Certificate{
69 | Certificate: [][]byte{derBytes, intermediateCert.Raw},
70 | PrivateKey: key,
71 | }, nil
72 | }
73 |
--------------------------------------------------------------------------------