-
Notifications
You must be signed in to change notification settings - Fork 24
/
baggage.go
158 lines (145 loc) · 4.87 KB
/
baggage.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
package baggage
import (
"net/url"
"strings"
envoy_core "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
otel_baggage "go.opentelemetry.io/otel/baggage"
"google.golang.org/protobuf/types/known/wrapperspb"
"github.com/fluxninja/aperture/v2/pkg/log"
flowlabel "github.com/fluxninja/aperture/v2/pkg/policies/flowcontrol/label"
)
// Headers is a header map in authz convention – keys are lowercase.
type Headers map[string]string
// Propagator defines how to extract flow labels (baggage) from and put into
// headers
//
// This interface is similar to go.opentelemetry.io/otel/propagation.TextMapPropagator:
// https://github.com/open-telemetry/opentelemetry-go/blob/v1.2.0/propagation/propagation.go#L91-L111
// but is designed to use authz-compatible types and conventions.
type Propagator interface {
// Extract extracts flow labels from headers
//
// The headers are expected to be in envoy's authz convention – with
// lower-case keys
Extract(headers Headers) flowlabel.FlowLabels
// Inject emits instructions for envoy how to inject flow labels into
// headers based on given flow labels and existing headers
//
// The returned list is expected to be put in
// CheckResponse.OkHttpResponse.Headers, so that envoy will take care of
// injecting appropriate headers.
Inject(flowLabels flowlabel.FlowLabels, headers Headers) ([]*envoy_core.HeaderValueOption, error)
}
// Prefixed puts each flow label into a separate header. Header name is
// concatenation of Prefix and flow label name.
type Prefixed struct {
// Header prefix, eg. "uberctx-" – in lower-case, as per envoy's authz
// convention – (note that this differs from golang's conventions))
Prefix string
}
// Extract extracts prefixed flow labels from headers.
func (p Prefixed) Extract(headers Headers) flowlabel.FlowLabels {
flowLabels := make(flowlabel.FlowLabels)
for key, val := range headers {
if strings.HasPrefix(key, p.Prefix) {
metaKey := strings.TrimPrefix(key, p.Prefix)
metaVal, err := url.QueryUnescape(val)
if err != nil {
log.Warn().Msg("Could not unescape flow label value in baggage")
} else {
flowLabels[metaKey] = flowlabel.FlowLabelValue{
Value: metaVal,
Telemetry: true,
}
}
}
}
return flowLabels
}
// Inject emits instructions for envoy how to inject flow labels into headers supported by prefixed propagator.
func (p Prefixed) Inject(
flowLabels flowlabel.FlowLabels,
headers Headers,
) ([]*envoy_core.HeaderValueOption, error) {
newHeaders := make([]*envoy_core.HeaderValueOption, 0, len(flowLabels))
for key, fl := range flowLabels {
if !fl.Telemetry {
continue
}
baggageKey := p.Prefix + key
newHeader := &envoy_core.HeaderValueOption{
Header: &envoy_core.HeaderValue{
Key: baggageKey,
// Note: not urlescaping the value – envoy will do it by itself.
Value: fl.Value,
},
Append: wrapperspb.Bool(false),
}
newHeaders = append(newHeaders, newHeader)
}
return newHeaders, nil
}
// W3Baggage handles baggage propagation in a single `baggage` header, as
// described in https://www.w3.org/TR/baggage/
//
// All baggage items are mapped to flow labels 1:1. This could be tweaked in future:
// * we can use some prefixing/filtering,
// * alternatively, we could put _all_ flow labels as a _single_ baggage item (eg. Fn-flow).
type W3Baggage struct{}
const (
w3BaggageHeaderName = "baggage"
hiddenPropertyKey = "hidden"
)
// Extract extracts flow labels from w3Baggage headers.
func (b W3Baggage) Extract(headers Headers) flowlabel.FlowLabels {
baggage, err := otel_baggage.Parse(headers[w3BaggageHeaderName])
if err != nil {
log.Warn().Err(err).Msg("Failed to parse baggage header")
return nil
}
flowLabels := make(flowlabel.FlowLabels)
for _, member := range baggage.Members() {
value, err := url.QueryUnescape(member.Value())
if err != nil {
log.Warn().Msg("Could not unescape flow label value in baggage")
continue
}
flowLabels[member.Key()] = flowlabel.FlowLabelValue{
Value: value,
Telemetry: true,
}
}
return flowLabels
}
// Inject emits instructions for envoy how to inject flow labels into headers supported by baggage propagator.
func (b W3Baggage) Inject(
flowLabels flowlabel.FlowLabels,
headers Headers,
) ([]*envoy_core.HeaderValueOption, error) {
members := make([]otel_baggage.Member, 0, len(flowLabels))
for k, v := range flowLabels {
if !v.Telemetry {
continue
}
member, err := otel_baggage.NewMember(k, v.Value)
if err != nil {
return nil, err
}
members = append(members, member)
}
if len(members) == 0 {
return nil, nil
}
baggage, err := otel_baggage.New(members...)
if err != nil {
return nil, err
}
_, baggageAlreadyExists := headers[w3BaggageHeaderName]
return []*envoy_core.HeaderValueOption{{
Header: &envoy_core.HeaderValue{
Key: w3BaggageHeaderName,
Value: baggage.String(),
},
Append: wrapperspb.Bool(baggageAlreadyExists),
}}, nil
}