This repository has been archived by the owner on Aug 11, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
/
main.go
189 lines (157 loc) · 4.27 KB
/
main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
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
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
package main
import (
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"os"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
"github.com/labstack/gommon/log"
"github.com/urfave/cli/v2"
)
const (
defaultOPAURL = "http://opa:8181/v1/data/httpapi/authz"
defaultServicePort = 8182
)
type Config struct {
OPA_URL string
Port int
}
type OPAResponse struct {
Result struct {
Allow bool `json:"allow"`
} `json:"result"`
}
func main() {
app := &cli.App{
Name: "traefik-opa-proxy",
Usage: "Translates OPA's decisions (allow: true or false) into HTTP status codes (200 or 403)",
Version: "v0.0.1",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "opa-url",
Value: defaultOPAURL,
Usage: "URL for the Open Policy Agent",
EnvVars: []string{"OPA_URL"},
},
&cli.IntFlag{
Name: "port",
Value: defaultServicePort,
Usage: "Port to run the service on",
EnvVars: []string{"SERVICE_PORT"},
},
},
Action: startProxy,
}
if err := app.Run(os.Args); err != nil {
log.Fatalf("Failed to start the app: %v", err)
}
}
func startProxy(c *cli.Context) error {
config := Config{
OPA_URL: c.String("opa-url"),
Port: c.Int("port"),
}
e := echo.New()
e.Logger.SetLevel(log.INFO)
e.Use(middleware.Logger())
e.Use(middleware.Recover())
e.Any("/*", getAuthorizationFromOPA(config.OPA_URL))
return e.Start(fmt.Sprintf(":%d", config.Port))
}
func getAuthorizationFromOPA(OPAURL string) echo.HandlerFunc {
return func(c echo.Context) error {
payload, err := buildPayload(c)
if err != nil {
return echoResponse(c, http.StatusInternalServerError, "Failed to build payload")
}
respBody, err := sendRequestToOPA(payload, OPAURL)
if err != nil {
return echoResponse(c, http.StatusInternalServerError, "Error interacting with OPA")
}
return determineResponse(c, respBody)
}
}
func buildPayload(c echo.Context) (map[string]interface{}, error) {
bodyBytes, err := io.ReadAll(c.Request().Body)
if err != nil {
return nil, err
}
formParams, err := c.FormParams()
if err != nil {
return nil, err
}
method := c.Request().Header.Get("X-Forwarded-Method")
if method == "" {
method = c.Request().Method
}
host := c.Request().Header.Get("X-Forwarded-Host")
if host == "" {
host = c.Request().Host
}
path := c.Request().Header.Get("X-Forwarded-Uri")
if path == "" {
path = c.Request().URL.Path
}
protocol := c.Request().Header.Get("X-Forwarded-Proto")
if path == "" {
protocol = c.Request().Proto
}
// Build the payload to match OPA-Envoy's expected input
payload := map[string]interface{}{
"attributes": map[string]interface{}{
"request": map[string]interface{}{
"http": map[string]interface{}{
"method": method,
"scheme": c.Request().URL.Scheme, // TODO: Is this correct?
"host": host,
"path": path,
"headers": map[string]interface{}{
"Authorization": c.Request().Header.Get("Authorization"),
// Add other headers if necessary
},
"body": string(bodyBytes),
"query_params": c.QueryParams(),
"form_params": formParams,
"protocol": protocol,
},
},
},
}
return payload, nil
}
func sendRequestToOPA(payload map[string]interface{}, OpaURL string) ([]byte, error) {
// Convert the map to a JSON string
jsonBytes, err := json.Marshal(payload)
if err != nil {
return nil, err
}
// URL-escape the JSON string
escapedString := url.QueryEscape(string(jsonBytes))
// Construct the full URL with the encoded query parameter
fullURL := fmt.Sprintf("%s?input=%s", OpaURL, escapedString)
// Make the GET request
resp, err := http.Get(fullURL)
if err != nil {
return nil, err
}
defer resp.Body.Close()
// Read and return the response body
return io.ReadAll(resp.Body)
}
func determineResponse(c echo.Context, body []byte) error {
var opaResp OPAResponse
if err := json.Unmarshal(body, &opaResp); err != nil {
return echoResponse(c, http.StatusInternalServerError, "Unknown OPA response format")
}
if opaResp.Result.Allow {
return echoResponse(c, http.StatusOK, "ok")
}
fmt.Println(opaResp)
return echoResponse(c, http.StatusForbidden, "forbidden")
}
func echoResponse(c echo.Context, statusCode int, message string) error {
return c.JSON(statusCode, map[string]string{"message": message})
}