Skip to content

Commit 9a1eaf3

Browse files
authored
MEDIUM: Add appname_header option (#41)
This options adds a header in the style of "X-Forwarded-For", but containing the app name that made the request. The information is extracted from the client certificate. Example configuration: ``` { "service": { "name": "socat", "port": 8181, "connect": { "sidecar_service": { "proxy": { "config": { "appname_header": "X-App" } } } } } } ```
1 parent b852672 commit 9a1eaf3

14 files changed

+155
-176
lines changed

consul/config.go

+7-6
Original file line numberDiff line numberDiff line change
@@ -46,12 +46,13 @@ func (n UpstreamNode) Equal(o UpstreamNode) bool {
4646
}
4747

4848
type Downstream struct {
49-
LocalBindAddress string
50-
LocalBindPort int
51-
Protocol string
52-
TargetAddress string
53-
TargetPort int
54-
EnableForwardFor bool
49+
LocalBindAddress string
50+
LocalBindPort int
51+
Protocol string
52+
TargetAddress string
53+
TargetPort int
54+
EnableForwardFor bool
55+
AppNameHeaderName string
5556

5657
TLS
5758
}

consul/watcher.go

+17-12
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,13 @@ type upstream struct {
2828
}
2929

3030
type downstream struct {
31-
LocalBindAddress string
32-
LocalBindPort int
33-
Protocol string
34-
TargetAddress string
35-
TargetPort int
36-
EnableForwardFor bool
31+
LocalBindAddress string
32+
LocalBindPort int
33+
Protocol string
34+
TargetAddress string
35+
TargetPort int
36+
EnableForwardFor bool
37+
AppNameHeaderName string
3738
}
3839

3940
type certLeaf struct {
@@ -128,6 +129,9 @@ func (w *Watcher) handleProxyChange(first bool, srv *api.AgentService) {
128129
if f, ok := srv.Proxy.Config["enable_forwardfor"].(bool); ok {
129130
w.downstream.EnableForwardFor = f
130131
}
132+
if a, ok := srv.Proxy.Config["appname_header"].(string); ok {
133+
w.downstream.AppNameHeaderName = a
134+
}
131135
}
132136

133137
keep := make(map[string]bool)
@@ -344,12 +348,13 @@ func (w *Watcher) genCfg() Config {
344348
ServiceID: w.service,
345349
CAsPool: w.certCAPool,
346350
Downstream: Downstream{
347-
LocalBindAddress: w.downstream.LocalBindAddress,
348-
LocalBindPort: w.downstream.LocalBindPort,
349-
TargetAddress: w.downstream.TargetAddress,
350-
TargetPort: w.downstream.TargetPort,
351-
Protocol: w.downstream.Protocol,
352-
EnableForwardFor: w.downstream.EnableForwardFor,
351+
LocalBindAddress: w.downstream.LocalBindAddress,
352+
LocalBindPort: w.downstream.LocalBindPort,
353+
TargetAddress: w.downstream.TargetAddress,
354+
TargetPort: w.downstream.TargetPort,
355+
Protocol: w.downstream.Protocol,
356+
EnableForwardFor: w.downstream.EnableForwardFor,
357+
AppNameHeaderName: w.downstream.AppNameHeaderName,
353358

354359
TLS: TLS{
355360
CAs: w.certCAs,

go.mod

+1-3
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,14 @@ require (
66
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a // indirect
77
github.com/criteo/haproxy-spoe-go v0.0.0-20190925130734-97891c13d324
88
github.com/d4l3k/messagediff v1.2.1 // indirect
9-
github.com/docker/go-units v0.4.0 // indirect
109
github.com/facebookgo/freeport v0.0.0-20150612182905-d4adf43b75b9
1110
github.com/go-openapi/analysis v0.19.0 // indirect
1211
github.com/go-openapi/jsonpointer v0.19.0 // indirect
1312
github.com/go-openapi/jsonreference v0.19.0 // indirect
1413
github.com/go-openapi/loads v0.19.0 // indirect
1514
github.com/go-openapi/runtime v0.19.0 // indirect
1615
github.com/go-openapi/spec v0.19.0 // indirect
17-
github.com/haproxytech/models v1.2.0
16+
github.com/haproxytech/models v1.2.4
1817
github.com/hashicorp/consul v1.7.2
1918
github.com/hashicorp/consul/api v1.4.0
2019
github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect
@@ -23,7 +22,6 @@ require (
2322
github.com/prometheus/client_golang v0.9.2
2423
github.com/sirupsen/logrus v1.4.2
2524
github.com/stretchr/testify v1.4.0
26-
golang.org/x/text v0.3.2 // indirect
2725
gopkg.in/d4l3k/messagediff.v1 v1.2.1
2826
gopkg.in/mcuadros/go-syslog.v2 v2.2.1
2927
)

go.sum

+8-145
Large diffs are not rendered by default.
+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package dataplane
2+
3+
import (
4+
"fmt"
5+
"net/http"
6+
7+
"github.com/haproxytech/models"
8+
)
9+
10+
func (c *Dataplane) HTTPRequestRules(parentType, parentName string) ([]models.HTTPRequestRule, error) {
11+
type resT struct {
12+
Data []models.HTTPRequestRule `json:"data"`
13+
}
14+
15+
var res resT
16+
17+
err := c.makeReq(http.MethodGet, fmt.Sprintf("/v1/services/haproxy/configuration/http_request_rules?parent_type=%s&parent_name=%s", parentType, parentName), nil, &res)
18+
if err != nil {
19+
return nil, err
20+
}
21+
22+
return res.Data, nil
23+
}
24+
25+
func (t *tnx) CreateHTTPRequestRule(parentType, parentName string, rule models.HTTPRequestRule) error {
26+
if err := t.ensureTnx(); err != nil {
27+
return err
28+
}
29+
return t.client.makeReq(http.MethodPost, fmt.Sprintf("/v1/services/haproxy/configuration/http_request_rules?parent_type=%s&parent_name=%s&transaction_id=%s", parentType, parentName, t.txID), rule, nil)
30+
}

haproxy/spoe.go

+9
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ func (h *SPOEHandler) Handler(args []spoe.Message) ([]spoe.Action, error) {
5050
}
5151

5252
authorized := err == nil
53+
sourceApp := ""
5354

5455
if authorized {
5556
certURI, err := connect.ParseCertURI(cert.URIs[0])
@@ -71,6 +72,9 @@ func (h *SPOEHandler) Handler(args []spoe.Message) ([]spoe.Action, error) {
7172
log.Debugf("spoe: auth response from %s authorized=%v", certURI.URI().String(), resp.Authorized)
7273

7374
authorized = resp.Authorized
75+
if sis, ok := certURI.(*connect.SpiffeIDService); ok {
76+
sourceApp = sis.Service
77+
}
7478
}
7579

7680
res := 1
@@ -83,6 +87,11 @@ func (h *SPOEHandler) Handler(args []spoe.Message) ([]spoe.Action, error) {
8387
Scope: spoe.VarScopeSession,
8488
Value: res,
8589
},
90+
spoe.ActionSetVar{
91+
Name: "source_app",
92+
Scope: spoe.VarScopeSession,
93+
Value: sourceApp,
94+
},
8695
}, nil
8796
}
8897
return nil, nil

haproxy/state/apply.go

+4
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,10 @@ func applyBackends(ha HAProxy, old, new []Backend) error {
139139
return err
140140
}
141141
}
142+
143+
for _, r := range newBack.HTTPRequestRules {
144+
err = ha.CreateHTTPRequestRule("backend", newBack.Backend.Name, r)
145+
}
142146
}
143147

144148
if !needCreate {

haproxy/state/apply_backend_test.go

+29
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,35 @@ func TestAddBackend(t *testing.T) {
2727
ha.RequireOps(t, RequireOp(haOpCreateBackend, "back"))
2828
}
2929

30+
func TestAddBackendHTTPRule(t *testing.T) {
31+
old := State{}
32+
new := State{
33+
Backends: []Backend{
34+
Backend{
35+
Backend: models.Backend{
36+
Name: "back",
37+
},
38+
HTTPRequestRules: []models.HTTPRequestRule{
39+
{
40+
HdrName: "X-App",
41+
HdrFormat: "%[var(sess.connect.source_app)]",
42+
},
43+
},
44+
},
45+
},
46+
}
47+
48+
ha := &fakeHA{}
49+
50+
err := Apply(ha, old, new)
51+
require.Nil(t, err)
52+
53+
ha.RequireOps(t,
54+
RequireOp(haOpCreateBackend, "back"),
55+
RequireOp(haOpCreateHTTPRequestRule, "back"),
56+
)
57+
}
58+
3059
func TestNoChangeBackend(t *testing.T) {
3160
old := State{
3261
Backends: []Backend{

haproxy/state/downstream.go

+10
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,16 @@ func generateDownstream(opts Options, certStore CertificateStore, cfg consul.Dow
109109
}
110110
}
111111

112+
// App name header
113+
if cfg.AppNameHeaderName != "" && beMode == models.BackendModeHTTP {
114+
be.HTTPRequestRules = append(be.HTTPRequestRules, models.HTTPRequestRule{
115+
ID: int64p(0),
116+
Type: models.HTTPRequestRuleTypeAddHeader,
117+
HdrName: cfg.AppNameHeaderName,
118+
HdrFormat: "%[var(sess.connect.source_app)]",
119+
})
120+
}
121+
112122
state.Backends = append(state.Backends, be)
113123

114124
return state, nil

haproxy/state/fake_ha_test.go

+9
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ const (
2121
haOpCreateFilter
2222
haOpCreateTCPRequestRule
2323
haOpCreateLogTargets
24+
haOpCreateHTTPRequestRule
2425
)
2526

2627
type fakeHAOp struct {
@@ -136,3 +137,11 @@ func (h *fakeHA) CreateLogTargets(parentType, parentName string, rule models.Log
136137
})
137138
return nil
138139
}
140+
141+
func (h *fakeHA) CreateHTTPRequestRule(parentType, parentName string, rule models.HTTPRequestRule) error {
142+
h.ops = append(h.ops, fakeHAOp{
143+
Type: haOpCreateHTTPRequestRule,
144+
Name: parentName,
145+
})
146+
return nil
147+
}

haproxy/state/from_ha.go

+13-3
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ type HAProxyRead interface {
1212
LogTargets(parentType, parentName string) ([]models.LogTarget, error)
1313
Filters(parentType, parentName string) ([]models.Filter, error)
1414
TCPRequestRules(parentType, parentName string) ([]models.TCPRequestRule, error)
15+
HTTPRequestRules(parentType, parentName string) ([]models.HTTPRequestRule, error)
1516
Backends() ([]models.Backend, error)
1617
Servers(beName string) ([]models.Server, error)
1718
}
@@ -100,10 +101,19 @@ func FromHAProxy(ha HAProxyRead) (State, error) {
100101
lt = &logTargets[0]
101102
}
102103

104+
reqRules, err := ha.HTTPRequestRules("backend", b.Name)
105+
if err != nil {
106+
return state, err
107+
}
108+
if len(reqRules) == 0 {
109+
reqRules = nil
110+
}
111+
103112
state.Backends = append(state.Backends, Backend{
104-
Backend: b,
105-
Servers: servers,
106-
LogTarget: lt,
113+
Backend: b,
114+
Servers: servers,
115+
LogTarget: lt,
116+
HTTPRequestRules: reqRules,
107117
})
108118
}
109119

haproxy/state/snapshot_test.go

+13-4
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,11 @@ import (
1313
func GetTestConsulConfig() consul.Config {
1414
return consul.Config{
1515
Downstream: consul.Downstream{
16-
LocalBindAddress: "127.0.0.2",
17-
LocalBindPort: 9999,
18-
TargetAddress: "128.0.0.5",
19-
TargetPort: 8888,
16+
LocalBindAddress: "127.0.0.2",
17+
LocalBindPort: 9999,
18+
TargetAddress: "128.0.0.5",
19+
TargetPort: 8888,
20+
AppNameHeaderName: "X-App",
2021
},
2122
Upstreams: []consul.Upstream{
2223
consul.Upstream{
@@ -131,6 +132,14 @@ func GetTestHAConfig(baseCfg string) State {
131132
Facility: models.LogTargetFacilityLocal0,
132133
Format: models.LogTargetFormatRfc5424,
133134
},
135+
HTTPRequestRules: []models.HTTPRequestRule{
136+
{
137+
ID: int64p(0),
138+
Type: models.HTTPRequestRuleTypeAddHeader,
139+
HdrName: "X-App",
140+
HdrFormat: "%[var(sess.connect.source_app)]",
141+
},
142+
},
134143
},
135144

136145
// upstream backend

haproxy/state/state.go

+4-3
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,10 @@ type Frontend struct {
1919
}
2020

2121
type Backend struct {
22-
Backend models.Backend
23-
LogTarget *models.LogTarget
24-
Servers []models.Server
22+
Backend models.Backend
23+
LogTarget *models.LogTarget
24+
Servers []models.Server
25+
HTTPRequestRules []models.HTTPRequestRule
2526
}
2627

2728
type State struct {

haproxy/state/states.go

+1
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ type HAProxy interface {
3333
CreateFilter(parentType, parentName string, filter models.Filter) error
3434
CreateTCPRequestRule(parentType, parentName string, rule models.TCPRequestRule) error
3535
CreateLogTargets(parentType, parentName string, rule models.LogTarget) error
36+
CreateHTTPRequestRule(parentType, parentName string, rule models.HTTPRequestRule) error
3637
}
3738

3839
func Generate(opts Options, certStore CertificateStore, oldState State, cfg consul.Config) (State, error) {

0 commit comments

Comments
 (0)