Add opt-in built-in Feishu OIDC issuer for OpenVPN
This commit is contained in:
1 parent
6ca609fde4
commit
63cdda4ae1
23 files changed
+1244
-17
No files matched your search
@@ -40,10 +40,19 @@ func routePattern(path string) string {
|
||||
// Render returns the HTTP-01 bootstrap site and the HTTPS site separately.
|
||||
// Enable the second site only after Certbot has issued the certificate.
|
||||
func Render(domain string, routes gin.RoutesInfo) (httpSite, httpsSite string, err error) {
|
||||
return RenderWithAuth(domain, routes, 0)
|
||||
}
|
||||
|
||||
// RenderWithAuth optionally exposes a single local OpenVPN browser-auth service.
|
||||
// All other unknown paths retain the existing 444 catch-all.
|
||||
func RenderWithAuth(domain string, routes gin.RoutesInfo, authPort int) (httpSite, httpsSite string, err error) {
|
||||
domain = strings.ToLower(strings.TrimSpace(domain))
|
||||
if !tlsservice.ValidDomain(domain) {
|
||||
return "", "", fmt.Errorf("请输入有效的单个完整域名")
|
||||
}
|
||||
if authPort != 0 && (authPort < 1024 || authPort > 65535) {
|
||||
return "", "", fmt.Errorf("认证端口无效")
|
||||
}
|
||||
httpSite = fmt.Sprintf(`# Install this HTTP site first. Certbot webroot: /var/lib/opsx-vpn/acme
|
||||
server {
|
||||
listen 80 default_server;
|
||||
@@ -84,10 +93,22 @@ server {
|
||||
}
|
||||
b.WriteString(" location ~ ^/tunnels/[0-9]+/edit$ {\n" + upstream + " }\n")
|
||||
b.WriteString(" location ~ ^/assets/[A-Za-z0-9_.-]+\\.(js|css|svg|png|woff2)$ {\n" + upstream + " }\n")
|
||||
// Public issuer endpoints are fixed: unknown /id/* requests still get 444.
|
||||
for _, path := range []string{"/id/.well-known/openid-configuration", "/id/jwks", "/id/authorize", "/id/feishu/callback", "/id/token", "/id/userinfo"} {
|
||||
fmt.Fprintf(&b, " location = %s {\n%s }\n", path, upstream)
|
||||
}
|
||||
if authPort != 0 {
|
||||
fmt.Fprintf(&b, " location ^~ /oauth2/ {\n proxy_set_header Host $host;\n proxy_set_header X-Forwarded-Proto https;\n proxy_set_header X-Real-IP $remote_addr;\n proxy_pass http://127.0.0.1:%d;\n }\n", authPort)
|
||||
}
|
||||
|
||||
seen := make(map[string]bool)
|
||||
var paths []string
|
||||
for _, route := range routes {
|
||||
// The issuer locations above are deliberately enumerated. Gin also
|
||||
// reports them as routes; emitting them twice makes nginx -t fail.
|
||||
if strings.HasPrefix(route.Path, "/id/") {
|
||||
continue
|
||||
}
|
||||
if !validRoute(route.Path) || seen[route.Path] {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -64,3 +64,25 @@ func TestRenderRejectsInjectedDomainAndRoute(t *testing.T) {
|
||||
t.Fatal("injected route accepted")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRenderIdentityAndSingleAuthPort(t *testing.T) {
|
||||
_, site, err := RenderWithAuth("vpn.example.com", gin.RoutesInfo{{Method: "GET", Path: "/api/health"}}, 19090)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, route := range []string{"location = /id/.well-known/openid-configuration", "location = /id/feishu/callback", "location = /id/token", "location ^~ /oauth2/", "proxy_pass http://127.0.0.1:19090;", "location / { return 444; }"} {
|
||||
if !strings.Contains(site, route) {
|
||||
t.Errorf("identity route missing: %s", route)
|
||||
}
|
||||
}
|
||||
if strings.Contains(site, "location ^~ /id/") || strings.Contains(site, "location ^~ /.well-known/") {
|
||||
t.Fatal("identity routes must stay allowlisted")
|
||||
}
|
||||
if _, _, err := RenderWithAuth("vpn.example.com", nil, 80); err == nil {
|
||||
t.Fatal("invalid auth port accepted")
|
||||
}
|
||||
_, withoutAuth, err := Render("vpn.example.com", nil)
|
||||
if err != nil || strings.Contains(withoutAuth, "location ^~ /oauth2/") {
|
||||
t.Fatal("auth component exposed without a configured port")
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user