-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmain.go
166 lines (143 loc) · 4.55 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
/*
Copyright 2024 Backplane BV
Copyright 2020 Google LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"fmt"
"log"
"log/slog"
"net/http"
"os"
"strings"
"aidanwoods.dev/go-paseto"
"github.com/urfave/cli/v2"
)
const (
proxyConfigHeader string = "X-Proxy-Config"
tokenKeyUpstreamToken string = "upstream-token"
)
var (
// version, commit, date, builtBy are provided by goreleaser during build
version = "dev"
commit = "dev"
date = "unknown"
builtBy = "unknown"
tokenEndpoints = map[string]*WWWAuthenticateData{} // mapping of registryHosts to token endpoint URLs
logger *slog.Logger
logLevel *slog.LevelVar
)
func init() {
logLevel = new(slog.LevelVar)
cli.VersionPrinter = func(c *cli.Context) {
fmt.Printf("registryproxy version %s; commit %s; built on %s; by %s\n", version, commit, date, builtBy)
}
}
func main() {
app := &cli.App{
Name: "registryproxy",
Version: version,
Usage: "reverse proxy for container image registries (like Docker Hub)",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "config",
Value: "",
Usage: "path to the config file",
},
&cli.StringFlag{
Name: "loglevel",
Value: "INFO",
Usage: "how verbosely to log, one of: DEBUG, INFO, WARN, ERROR",
},
},
Action: func(ctx *cli.Context) error {
setLogLevel(ctx.String("loglevel"))
logger = slog.New(slog.NewTextHandler(
os.Stderr,
&slog.HandlerOptions{
Level: logLevel,
}),
)
logger.Info("registryproxy starting up",
"version", version,
"commit", commit,
"date", date,
"builder", builtBy,
)
Serve(ctx.String("config"))
return nil
},
}
if err := app.Run(os.Args); err != nil {
log.Fatal(err)
}
}
func Serve(configPath string) {
// load the yaml config file
config, err := LoadConfig(configPath)
if err != nil {
logger.Error("config loading error", "error", err)
os.Exit(1)
}
config.Log()
// the secret key is used to process the PASETO tokens we issue to clients
if config.SecretKey == "" {
logger.Error("SecretKey not found in config")
os.Exit(1)
}
pasetoSecretKey, err := paseto.V4SymmetricKeyFromHex(config.SecretKey)
if err != nil {
logger.Error("Failed to parse PASETO symmetric key", "err", err)
os.Exit(1)
}
// set up http handlers for each proxy
mux := http.NewServeMux()
mux.Handle("/_token", NewTokenProxy(config, pasetoSecretKey))
mux.HandleFunc("/v2/", ServeServiceDiscoveryEndpoint) // handles exactly "/v2/" (nothing under it)
for _, proxy := range config.Proxies {
if _, ok := tokenEndpoints[proxy.RegistryHost]; !ok {
// if the token endpoint for the given RegistryHost isn't in
// tokenEndpoints we look it up, then add it
endpoint, err := DiscoverTokenEndpoint(proxy.RegistryHost)
if err != nil {
logger.Error("unable to discover token endpoint", "registry", proxy.RegistryHost, "error", err)
os.Exit(1)
}
tokenEndpoints[proxy.RegistryHost] = endpoint
}
proxyPath := fmt.Sprintf("/v2/%s/", strings.Trim(proxy.LocalPrefix, "/"))
logger.Info("setup handler", "path", proxyPath, "proxy", proxy.LocalPrefix)
mux.Handle(proxyPath, NewRegistryProxy(proxy, pasetoSecretKey, config.ProxyFQDN))
}
// serve
hostport := fmt.Sprintf("%s:%s", config.ListenAddr, config.ListenPort)
logger.Info("listening for network connections", "addr", hostport)
if err := http.ListenAndServe(hostport, PanicLogger(mux)); err != http.ErrServerClosed {
logger.Error("unable to start network listener", "error", err)
os.Exit(1)
}
logger.Info("server shutdown successfully")
}
// PanicLogger intends to log something when an http handler panics
func PanicLogger(next http.Handler) http.Handler {
// Note: this needs testing/validation, the entire concept of this middleware
// may be the result of several wrong assumptions
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
defer func() {
if err := recover(); err != nil {
logger.Error("PanicLogger recovered in HTTP handler", "handler", err)
http.Error(rw, "Internal Server Error", http.StatusInternalServerError)
}
}()
next.ServeHTTP(rw, req)
})
}