Browse Source

Merge ef8cbcfaf4 into 5a4395fd12

pull/4590/merge
Maksim Nabokikh 3 days ago committed by GitHub
parent
commit
be0cd8c44b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 2
      .github/workflows/ci.yaml
  2. 223
      connector/authproxy/authproxy_integration_test.go
  3. 55
      connector/authproxy/testdata/nginx.conf
  4. 4
      docker-compose.override.yaml.dist
  5. 9
      docker-compose.test.yaml
  6. 10
      docker-compose.yaml

2
.github/workflows/ci.yaml

@ -184,6 +184,8 @@ jobs:
DEX_KUBERNETES_CONFIG_PATH: ~/.kube/config
DEX_AUTHPROXY_URL: http://localhost:18081
lint:
name: Lint
runs-on: ubuntu-latest

223
connector/authproxy/authproxy_integration_test.go

@ -0,0 +1,223 @@
package authproxy
import (
"encoding/json"
"fmt"
"io"
"log/slog"
"net"
"net/http"
"os"
"sync"
"testing"
"time"
"github.com/dexidp/dex/connector"
)
// The fixed port on which the Go test backend listens.
// nginx is configured to proxy_pass to host.docker.internal:18562.
const testBackendPort = "18562"
const testAuthProxyURLEnv = "DEX_AUTHPROXY_URL"
// identityResult holds the result of HandleCallback invoked on the proxied request.
type identityResult struct {
Identity connector.Identity `json:"identity"`
Error string `json:"error,omitempty"`
}
// startTestBackend starts an HTTP server that receives proxied requests from nginx,
// invokes HandleCallback on each request, and returns the identity as JSON.
// The caller must call the returned cleanup function to shut down the server.
func startTestBackend(t *testing.T, conn *callback) (cleanup func()) {
t.Helper()
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
ident, err := conn.HandleCallback(connector.Scopes{Groups: true}, nil, r)
result := identityResult{Identity: ident}
if err != nil {
result.Error = err.Error()
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(result)
})
listener, err := net.Listen("tcp", ":"+testBackendPort)
if err != nil {
t.Fatalf("failed to listen on port %s: %v", testBackendPort, err)
}
server := &http.Server{Handler: mux}
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
if err := server.Serve(listener); err != nil && err != http.ErrServerClosed {
t.Errorf("backend server error: %v", err)
}
}()
return func() {
server.Close()
wg.Wait()
}
}
// subtest describes a single integration test case for the authproxy connector.
type integrationSubtest struct {
name string
config Config
// path is the nginx location path to hit (e.g., "/default", "/minimal").
path string
wantErr bool
want connector.Identity
}
func TestIntegrationAuthProxy(t *testing.T) {
proxyURL := os.Getenv(testAuthProxyURLEnv)
if proxyURL == "" {
t.Skipf("test environment variable %q not set, skipping authproxy integration tests", testAuthProxyURLEnv)
}
tests := []integrationSubtest{
{
name: "all headers set",
config: Config{},
path: "/default",
want: connector.Identity{
UserID: "uid-12345",
Username: "testuser",
PreferredUsername: "Test User",
Email: "testuser@example.com",
EmailVerified: true,
Groups: []string{"group1", "group2", "group3"},
},
},
{
name: "minimal headers - fallback to X-Remote-User",
config: Config{},
path: "/minimal",
want: connector.Identity{
UserID: "janedoe",
Username: "janedoe",
PreferredUsername: "janedoe",
Email: "janedoe",
EmailVerified: true,
},
},
{
name: "no auth headers - expect error",
config: Config{},
path: "/no-headers",
wantErr: true,
},
{
name: "custom group separator",
config: Config{
GroupHeaderSeparator: ";",
},
path: "/custom-separator",
want: connector.Identity{
UserID: "uid-99999",
Username: "johndoe",
PreferredUsername: "John Doe",
Email: "johndoe@example.com",
EmailVerified: true,
Groups: []string{"admins", "developers", "ops"},
},
},
{
name: "all headers set with static groups",
config: Config{
Groups: []string{"static-group1", "static-group2"},
},
path: "/default",
want: connector.Identity{
UserID: "uid-12345",
Username: "testuser",
PreferredUsername: "Test User",
Email: "testuser@example.com",
EmailVerified: true,
Groups: []string{"group1", "group2", "group3", "static-group1", "static-group2"},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
l := slog.New(slog.DiscardHandler)
c, err := tt.config.Open("test-authproxy", l)
if err != nil {
t.Fatalf("failed to open connector: %v", err)
}
cb := c.(*callback)
cleanup := startTestBackend(t, cb)
defer cleanup()
// Give the backend a moment to start.
time.Sleep(50 * time.Millisecond)
// Make a request through the nginx proxy.
reqURL := fmt.Sprintf("%s%s", proxyURL, tt.path)
resp, err := http.Get(reqURL)
if err != nil {
t.Fatalf("failed to make request through proxy: %v", err)
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
t.Fatalf("failed to read response body: %v", err)
}
var result identityResult
if err := json.Unmarshal(body, &result); err != nil {
t.Fatalf("failed to decode response: %v\nbody: %s", err, string(body))
}
if tt.wantErr {
if result.Error == "" {
t.Fatal("expected error from HandleCallback, got none")
}
return
}
if result.Error != "" {
t.Fatalf("unexpected error from HandleCallback: %s", result.Error)
}
got := result.Identity
if got.UserID != tt.want.UserID {
t.Errorf("UserID: got %q, want %q", got.UserID, tt.want.UserID)
}
if got.Username != tt.want.Username {
t.Errorf("Username: got %q, want %q", got.Username, tt.want.Username)
}
if got.PreferredUsername != tt.want.PreferredUsername {
t.Errorf("PreferredUsername: got %q, want %q", got.PreferredUsername, tt.want.PreferredUsername)
}
if got.Email != tt.want.Email {
t.Errorf("Email: got %q, want %q", got.Email, tt.want.Email)
}
if got.EmailVerified != tt.want.EmailVerified {
t.Errorf("EmailVerified: got %v, want %v", got.EmailVerified, tt.want.EmailVerified)
}
if len(got.Groups) != len(tt.want.Groups) {
t.Errorf("Groups length: got %d, want %d (got: %v, want: %v)", len(got.Groups), len(tt.want.Groups), got.Groups, tt.want.Groups)
} else {
for i := range tt.want.Groups {
if got.Groups[i] != tt.want.Groups[i] {
t.Errorf("Groups[%d]: got %q, want %q", i, got.Groups[i], tt.want.Groups[i])
}
}
}
})
}
}

55
connector/authproxy/testdata/nginx.conf vendored

@ -0,0 +1,55 @@
events {
worker_connections 128;
}
http {
# The upstream is the Go test server started on a fixed port.
upstream backend {
server host.docker.internal:18562;
}
server {
listen 80;
# Default headers scenario: all X-Remote-* headers set.
location /default {
proxy_set_header X-Remote-User "testuser";
proxy_set_header X-Remote-User-Id "uid-12345";
proxy_set_header X-Remote-User-Email "testuser@example.com";
proxy_set_header X-Remote-User-Name "Test User";
proxy_set_header X-Remote-Group "group1, group2, group3";
proxy_pass http://backend;
}
# Only X-Remote-User header set; other fields should fall back to it.
location /minimal {
proxy_set_header X-Remote-User "janedoe";
proxy_set_header X-Remote-User-Id "";
proxy_set_header X-Remote-User-Email "";
proxy_set_header X-Remote-User-Name "";
proxy_set_header X-Remote-Group "";
proxy_pass http://backend;
}
# No auth headers: connector should return an error.
location /no-headers {
proxy_set_header X-Remote-User "";
proxy_set_header X-Remote-User-Id "";
proxy_set_header X-Remote-User-Email "";
proxy_set_header X-Remote-User-Name "";
proxy_set_header X-Remote-Group "";
proxy_pass http://backend;
}
# Custom separator scenario: groups separated by ";".
location /custom-separator {
proxy_set_header X-Remote-User "johndoe";
proxy_set_header X-Remote-User-Id "uid-99999";
proxy_set_header X-Remote-User-Email "johndoe@example.com";
proxy_set_header X-Remote-User-Name "John Doe";
proxy_set_header X-Remote-Group "admins;developers;ops";
proxy_pass http://backend;
}
}
}

4
docker-compose.override.yaml.dist

@ -21,3 +21,7 @@ services:
ports:
- "127.0.0.1:389:389"
- "127.0.0.1:636:636"
authproxy-nginx:
ports:
- "127.0.0.1:18081:80"

9
docker-compose.test.yaml

@ -16,3 +16,12 @@ services:
volumes:
- ./connector/ldap/testdata/certs:/container/service/slapd/assets/certs
- ./connector/ldap/testdata/schema.ldif:/container/service/slapd/assets/config/bootstrap/ldif/99-schema.ldif
authproxy-nginx:
image: nginx:1.27-alpine
volumes:
- ./connector/authproxy/testdata/nginx.conf:/etc/nginx/nginx.conf:ro
extra_hosts:
- "host.docker.internal:host-gateway"
ports:
- 18081:80

10
docker-compose.yaml

@ -74,3 +74,13 @@ services:
- IPC_LOCK
ports:
- 8210:8200
authproxy-nginx:
image: nginx:1.27-alpine
volumes:
- ./connector/authproxy/testdata/nginx.conf:/etc/nginx/nginx.conf:ro
extra_hosts:
- "host.docker.internal:host-gateway"
ports:
- 18081:80

Loading…
Cancel
Save