-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmain.go
More file actions
259 lines (218 loc) · 6.65 KB
/
Copy pathmain.go
File metadata and controls
259 lines (218 loc) · 6.65 KB
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
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
package main
import (
"gopkg.in/yaml.v3"
"encoding/json"
"github.com/xeipuuv/gojsonschema"
"fmt"
"io"
"os"
"strings"
"time"
"log"
"net"
"net/http"
"net/url"
"crypto/tls"
)
type Config struct {
Users map[string]UserConfig `yaml:"users"`
}
type UserConfig struct {
Password string `yaml:"password"`
Path string `yaml:"path"`
Methods []string `yaml:"methos"`
}
func main() {
// Get YAML configuration file from environment variable
configFile := os.Getenv("AUTH_CONFIG_FILE")
if configFile == "" {
configFile = "config.yaml" // Default file
}
// Validate the YAML configuration file with the schema
validateYAMLWithSchema(configFile)
// Parse configuration file after validation
file, err := os.Open(configFile)
if err != nil {
log.Fatalf("Failed to open config file: %v", err)
}
defer file.Close()
var config Config
if err := yaml.NewDecoder(file).Decode(&config); err != nil {
log.Fatalf("Failed to parse config file: %v", err)
}
// Get environment variables
authType := os.Getenv("AUTH_TYPE")
if authType == "" {
authType = "none"
}
authUpstream := os.Getenv("AUTH_UPSTREAM")
if authUpstream == "" {
log.Fatal("AUTH_UPSTREAM must be set")
}
authPort := os.Getenv("AUTH_PORT")
if authPort == "" {
authPort = "8080"
}
// Configure HTTP handler
handler := func(w http.ResponseWriter, r *http.Request) {
logRequest(r) // Log each request
if authType != "none" {
username, password, ok := r.BasicAuth()
if !ok {
// Send `WWW-Authenticate` header to trigger login popup
w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
userConfig, exists := config.Users[username]
if !exists || userConfig.Password != password {
// Send `WWW-Authenticate` header to trigger login popup
w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
// Allow exact matches and sub-paths
if r.URL.Path != strings.TrimSuffix(userConfig.Path, "/") &&
!strings.HasPrefix(r.URL.Path, userConfig.Path) {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
// Check if the HTTP method is allowed
if !methodAllowed(r.Method, userConfig.Methods) {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
}
// Proxy request to upstream
proxyRequest(w, r, authUpstream)
}
http.HandleFunc("/", handler)
// Start server
log.Printf("Starting server on port %s...", authPort)
log.Fatal(http.ListenAndServe(":"+authPort, nil))
}
func methodAllowed(method string, allowedMethods []string) bool {
for _, m := range allowedMethods {
if strings.EqualFold(m, method) {
return true
}
}
return false
}
func proxyRequest(w http.ResponseWriter, r *http.Request, upstream string) {
// Parse the upstream URL
parsedUpstream, err := url.Parse(upstream)
if err != nil {
log.Printf("Invalid upstream URL: %v", err)
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
return
}
// Log the upstream address and schema
log.Printf("Proxying request to upstream: %s (schema: %s)", parsedUpstream.Host, parsedUpstream.Scheme)
// Modify the request URL to point to the upstream server
r.URL.Scheme = parsedUpstream.Scheme
r.URL.Host = parsedUpstream.Host
r.RequestURI = ""
r.Host = parsedUpstream.Host
// Set timeouts for the HTTP client to avoid indefinite waiting
timeout := 10 * time.Second // Set a reasonable timeout (e.g., 10 seconds)
// Create an HTTP client with TLS configuration allow Insecure TLS
var client *http.Client
if parsedUpstream.Scheme == "https" {
// Create a custom HTTP client that conditionally skips certificate verification
customTransport := &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true, // Use the value from AUTH_INSEC_UPSTREAM
},
// Set the timeout for the transport layer
DialContext: (&net.Dialer{
Timeout: timeout,
}).DialContext,
// Set the TLS handshake timeout (separate from the dial timeout)
TLSHandshakeTimeout: timeout,
}
client = &http.Client{
Transport: customTransport,
Timeout: timeout, // Set overall client timeout
}
} else {
// Default HTTP client for non-HTTPS
client = &http.Client{
Timeout: timeout, // Set overall client timeout
}
}
// Forward the request
resp, err := client.Do(r)
if err != nil {
log.Printf("Error connecting to upstream server: %v", err)
http.Error(w, "Failed to connect to upstream server", http.StatusBadGateway)
return
}
defer resp.Body.Close()
// Copy response from upstream server to the client
for k, v := range resp.Header {
w.Header()[k] = v
}
w.WriteHeader(resp.StatusCode)
if _, err := io.Copy(w, resp.Body); err != nil {
log.Printf("Failed to copy response body: %v", err)
}
}
func logRequest(r *http.Request) {
// logRequest logs details about the incoming HTTP request
log.Printf("Request: Method=%s, Path=%s, RemoteAddr=%s, Headers=%v",
r.Method, r.URL.Path, r.RemoteAddr, r.Header)
}
func validateYAMLWithSchema(configFile string) {
// Load the YAML schema file
schemaFile, err := os.Open("yaml-schema.json") // Change to your actual JSON schema file path
if err != nil {
log.Fatalf("Failed to open schema file: %v", err)
}
defer schemaFile.Close()
// Read the schema content
schemaContent, err := io.ReadAll(schemaFile)
if err != nil {
log.Fatalf("Failed to read schema file: %v", err)
}
// Parse JSON schema
schemaLoader := gojsonschema.NewStringLoader(string(schemaContent))
// Load the YAML file
yamlFile, err := os.Open(configFile)
if err != nil {
log.Fatalf("Failed to open YAML file: %v", err)
}
defer yamlFile.Close()
// Read YAML content
yamlContent, err := io.ReadAll(yamlFile)
if err != nil {
log.Fatalf("Failed to read YAML file: %v", err)
}
// Parse YAML content
var yamlData interface{}
err = yaml.Unmarshal(yamlContent, &yamlData)
if err != nil {
log.Fatalf("Failed to parse YAML: %v", err)
}
// Convert YAML to JSON format
jsonData, err := json.Marshal(yamlData)
if err != nil {
log.Fatalf("Failed to convert YAML to JSON: %v", err)
}
// Load the JSON data for validation
document := gojsonschema.NewStringLoader(string(jsonData))
// Validate the YAML against the JSON schema
result, err := gojsonschema.Validate(schemaLoader, document)
if err != nil {
log.Fatalf("YAML validation failed: %v", err)
}
if result.Valid() {
fmt.Println("YAML file is valid according to the JSON schema!")
} else {
fmt.Printf("The document is not valid. See errors:\n")
for _, desc := range result.Errors() {
fmt.Printf("- %s\n", desc)
}
os.Exit(1) // Exit with an error code if validation fails
}
}