a (hacky, wip) multi-tenant oidc-terminating reverse proxy, written in anger on top of pingora
1syntax = "proto3";
2
3package config.format;
4
5// the root config
6//
7// each of these fields will show up at the root of your config file
8//
9// at the minimum, you'll need at least 1 `domains` entry to define your
10// sites, and one `bind_to_tcp` entry to define how the proxy itself
11// serves content.
12message Config {
13 // the domains to serve
14 map<string, Domain> domains = 1;
15
16 // bind to tcp ports, with optional tls
17 repeated TCPBinding bind_to_tcp = 2;
18
19 // lower-level pingora config
20 //
21 // (you probably don't need to touch this)
22 Pingora pingora = 3;
23}
24
25// a single, served domain
26//
27// you'll want at least 1 backend (`https`, `http`, or `uds`),
28// but you can repeat each one to define multiple backends
29// and load-balance between them, and you can (if you really need
30// to) mix and match types.
31//
32// you may also want an (optional) `oidc_auth` configuration to turn on
33// authentication-termination
34//
35// you can also use `manage_headers` to inject things like `X-Forwarded-For`,
36// or clear headers that you don't want going to your backend.
37message Domain {
38 // require oidc auth if this is set
39 optional OIDC oidc_auth = 1;
40
41 // https backends
42 repeated HTTPSBackend https = 3;
43 // http backends
44 repeated HTTPSBackend http = 7;
45 // unix domain socket backends
46 repeated UDSBackend uds = 4;
47
48 enum TLSMode {
49 // don't support redirects, they can be _very_ unsafe. just use hsts
50
51 // only allow https, no redirect
52 TLS_MODE_ONLY = 0;
53 // allow http, for testing purposes
54 TLS_MODE_UNSAFE_ALLOW_HTTP = 1;
55 }
56
57 // allow disabling TLS termination, for testing
58 TLSMode tls_mode = 5;
59
60 // set or clear headers on the backend request
61 ManageHeaders manage_headers = 6;
62
63 // configure tls settings
64 message TLS {
65 // path to the (public) tls certificate, with all intermediate certificates (fullchain.pem for most acme clients)
66 string cert_path = 1;
67 // path to the (privat) tls key
68 string key_path = 2;
69 // override the default (using the domain name) for SNI purposes
70 // useful if the domain contains a port
71 optional string sni = 3;
72 }
73
74 // configure tls
75 //
76 // you should _always_ have this
77 TLS tls = 8;
78}
79
80// configure oidc/oauth v2.1 termination
81message OIDC {
82 // the base oidc discovery url, without the `.well-known/openid-configuration` part
83 //
84 // per [OIDC Discovery 1.0](https://openid.net/specs/openid-connect-discovery-1_0.html)
85 // and [RFC 8414](https://datatracker.ietf.org/doc/html/rfc8414).
86 string discovery_url_base = 1;
87
88 // your oauth client id, from your oauth provider
89 string client_id = 2;
90 // your oauth client secret, from your oauth provider
91 string client_secret_path = 3;
92
93 // a set of scopes you wish to ask the sever for
94 //
95 // (`openid` will always be automatically included)
96 Scopes scopes = 4;
97 // map returned "claims" (pieces of user information)
98 // to header in the backend request.
99 //
100 // for example, you can use this to tell backend services the name of the
101 // authenticated user.
102 Claims claims = 5;
103
104 // per oidc core v1-with-errata-2§3.1.3.7 point 6, we _may_ skip validation
105 // of the id token if it was received over tls. which it will be, in our
106 // case. some folks may want to be extra paranoid, but generally you either
107 // trust tls, or you can't trust discovery, and thus can't trust the jwks info,
108 // so default this to false.
109 //
110 // generally, you can leave this off.
111 bool validate_with_jwk = 6;
112
113 // where to redirect to on logout
114 string logout_url = 7;
115}
116
117// scopes to ask the server for
118message Scopes {
119 // the scopes you need from the server for your claims map to work, or that
120 // you want to request to ensure that the user is authorized to continue to
121 // your site.
122 repeated string required = 2;
123
124 // optional scopes, not requested from the server
125 //
126 // not currently used
127 repeated string optional = 1;
128}
129
130// information on how to process returned claims
131message Claims {
132 // maps a claim to a header name & value
133 message ClaimToHeader {
134 // the name of the claim
135 string claim = 1;
136 // the name to the header
137 string header = 2;
138 // how to serialize compound (object, array) typed-claims
139 optional Serialization serialize_as = 3;
140 }
141
142 // map the given claims to a header in the backend request
143 //
144 // headers specified here with no corresponding claim value will be wiped
145 repeated ClaimToHeader claim_to_header = 1;
146
147 // how to serialize compound (object, array) typed-claims
148 //
149 // this doesn't cover _deeply_ nested identical objects (e.g.
150 // arrays-of-arrays), or selecting/projecting, but covers most usecases. in
151 // theory i could implement, say, jq or something but that feels like
152 // overkill.
153 //
154 // values that are `null` will always be skipped, and map entries with a null
155 // value with also be skipped.
156 message Serialization {
157 // how to join keys to values (e.g. `=` means `key=value`)
158 optional string join_keys_and_values_with = 1;
159 // how to join key-value pairs to each other (e.g. `; ` means `key=value; key=value`)
160 optional string join_key_value_pairs_with = 2;
161 // how to join array items to each other (e.g. `, ` means `item, item, item`)
162 optional string join_array_items_with = 3;
163 }
164}
165
166// a standard https backend
167message HTTPSBackend {
168 // full ipv4 or v6 address, including port
169 string addr = 1;
170 // weight of this backend, if load-balancing
171 optional uint64 weight = 2;
172
173 // skip verifying certificates on tls backends
174 //
175 // useful if your backend certs are self-signed,
176 // or for some reason not valid for the backend itself.
177 //
178 // generally prefer to use `ca_path` instead.
179 bool skip_verifying_certs = 3;
180
181 // path to the CA file used to verify the backend's certs, on tls backends
182 //
183 // useful if the backend is using self-signed certs
184 optional string ca_path = 4;
185}
186
187// a unix domain socket backend
188message UDSBackend {
189 // path to the uds socket
190 string path = 1;
191 // weight of this backend, if load-balancing
192 optional uint64 weight = 2;
193}
194
195// these headers will be set if they are set to something
196//
197// either way, they will be wiped if the client tries to send them
198message ManageHeaders {
199 // set the given header to be the request host
200 optional string host = 1;
201 // set an `X-Forwarded-For`-style header, appending `,<remote_addr>` to any existing value
202 optional string x_forwarded_for = 2;
203 // set an `X-Forwarded-Proto`-style header to the original scheme of the request
204 optional string x_forwarded_proto = 3;
205 // set an `X-Real-IP`-style header (i.e. _just_ the remote address)
206 optional string remote_addr = 4;
207
208 // always clear these headers
209 repeated string always_clear = 5;
210}
211
212// equivalent to [`pingora::server::configuration::Config`]
213//
214// See [pingora](https://docs.rs/pingora/latest/pingora/server/configuration/struct.ServerConf.html)
215// for details on what these do
216message Pingora {
217 optional uint64 version = 1;
218 optional bool daemon = 2;
219 optional string error_log = 3;
220 optional string pid_file = 4;
221 optional string upgrade_sock = 5;
222 optional string user = 6;
223 optional string group = 7;
224 optional uint64 threads = 8;
225 optional uint64 listener_tasks_per_fd = 9;
226 optional bool work_stealing = 10;
227 optional string ca_file = 11;
228 optional uint64 grace_period_seconds = 12;
229 optional uint64 graceful_shutdown_timeout_seconds = 13;
230 repeated string client_bind_to_ipv4 = 14;
231 repeated string client_bind_to_ipv6 = 15;
232 optional uint64 upstream_keepalive_pool_size = 16;
233 optional uint64 upstream_connect_offload_threadpools = 17;
234 optional uint64 upstream_connect_offload_thread_per_pool = 18;
235 optional bool upstream_debug_ssl_keylog = 19;
236 optional uint64 max_retries = 20;
237 optional uint64 upgrade_sock_connect_accept_max_retries = 21;
238
239}
240
241// bind to a tcp port
242//
243// this configures which ports you'll serve https & grpc traffic on.
244//
245// you'll want to chose an address.
246//
247// tls is configured per-domain in the domain settings.
248message TCPBinding {
249 // TODO(feature): default tls settings
250
251 // host an port to bind to
252 string addr = 1;
253
254 // TODO(feature): surface tcp options from pingora
255}