|
| 1 | +package authn |
| 2 | + |
| 3 | +import ( |
| 4 | + "context" |
| 5 | + "crypto/tls" |
| 6 | + "encoding/pem" |
| 7 | + "fmt" |
| 8 | + "net/http" |
| 9 | + "net/http/httptest" |
| 10 | + "os" |
| 11 | + "testing" |
| 12 | +) |
| 13 | + |
| 14 | +// newTestOIDCIssuer creates an HTTPS test server serving minimal OIDC discovery and JWKS endpoints. |
| 15 | +func newTestOIDCIssuer() *httptest.Server { |
| 16 | + var ts *httptest.Server |
| 17 | + ts = httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
| 18 | + switch r.URL.Path { |
| 19 | + case "/.well-known/openid-configuration": |
| 20 | + w.Header().Set("Content-Type", "application/json") |
| 21 | + // Use the test server's URL as issuer and JWKS URI. |
| 22 | + fmt.Fprintf(w, `{"issuer": "%s", "jwks_uri": "%s/jwks"}`, ts.URL, ts.URL) |
| 23 | + case "/jwks": |
| 24 | + w.Header().Set("Content-Type", "application/json") |
| 25 | + fmt.Fprint(w, `{"keys": []}`) |
| 26 | + default: |
| 27 | + http.NotFound(w, r) |
| 28 | + } |
| 29 | + })) |
| 30 | + return ts |
| 31 | +} |
| 32 | + |
| 33 | +func TestNewOIDCAuthenticator(t *testing.T) { |
| 34 | + // Create a dummy OIDC issuer (HTTPS). |
| 35 | + ts := newTestOIDCIssuer() |
| 36 | + defer ts.Close() |
| 37 | + |
| 38 | + // Override the default transport to skip TLS verification. |
| 39 | + // This is needed because ts uses a self-signed certificate. |
| 40 | + // This should be legit for testing purpose, even with parallel test cases run at the same time. |
| 41 | + origTransport := http.DefaultTransport |
| 42 | + http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: true} |
| 43 | + defer func() { |
| 44 | + http.DefaultTransport = origTransport |
| 45 | + }() |
| 46 | + |
| 47 | + // common dummy configuration values. |
| 48 | + baseConfig := OIDCConfig{ |
| 49 | + IssuerURL: ts.URL, |
| 50 | + ClientID: "test-client", |
| 51 | + UsernameClaim: "email", |
| 52 | + UsernamePrefix: "", |
| 53 | + GroupsClaim: "groups", |
| 54 | + GroupsPrefix: "", |
| 55 | + SupportedSigningAlgs: []string{"RS256"}, |
| 56 | + } |
| 57 | + |
| 58 | + t.Run("EmptyCAFile", func(t *testing.T) { |
| 59 | + // CAFile is empty; the authenticator should default to using the host's trust store. |
| 60 | + config := baseConfig |
| 61 | + config.CAFile = "" |
| 62 | + |
| 63 | + auth, err := NewOIDCAuthenticator(context.Background(), &config) |
| 64 | + if err != nil { |
| 65 | + t.Fatalf("expected no error, got: %v", err) |
| 66 | + } |
| 67 | + if auth.dynamicClientCA != nil { |
| 68 | + t.Errorf("expected dynamicClientCA to be nil when CAFile is empty, got non-nil") |
| 69 | + } |
| 70 | + }) |
| 71 | + |
| 72 | + t.Run("ValidCAFile", func(t *testing.T) { |
| 73 | + // Extract the test server's certificate and write it to a temporary file. |
| 74 | + if ts.TLS == nil || len(ts.TLS.Certificates) == 0 { |
| 75 | + t.Fatal("test server does not have a TLS certificate") |
| 76 | + } |
| 77 | + certBytes := ts.TLS.Certificates[0].Certificate[0] |
| 78 | + validCAPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certBytes}) |
| 79 | + |
| 80 | + tmpFile, err := os.CreateTemp("", "test-ca-*.pem") |
| 81 | + if err != nil { |
| 82 | + t.Fatalf("failed to create temporary file: %v", err) |
| 83 | + } |
| 84 | + defer os.Remove(tmpFile.Name()) |
| 85 | + |
| 86 | + if _, err = tmpFile.Write(validCAPEM); err != nil { |
| 87 | + t.Fatalf("failed to write certificate to temporary file: %v", err) |
| 88 | + } |
| 89 | + tmpFile.Close() |
| 90 | + |
| 91 | + config := baseConfig |
| 92 | + config.CAFile = tmpFile.Name() |
| 93 | + |
| 94 | + auth, err := NewOIDCAuthenticator(context.Background(), &config) |
| 95 | + if err != nil { |
| 96 | + t.Fatalf("expected no error, got: %v", err) |
| 97 | + } |
| 98 | + if auth.dynamicClientCA == nil { |
| 99 | + t.Errorf("expected dynamicClientCA to be non-nil when a valid CAFile is provided") |
| 100 | + } |
| 101 | + }) |
| 102 | + |
| 103 | + t.Run("InvalidCAFile", func(t *testing.T) { |
| 104 | + config := baseConfig |
| 105 | + config.CAFile = "non-existent-file.pem" |
| 106 | + |
| 107 | + _, err := NewOIDCAuthenticator(context.Background(), &config) |
| 108 | + if err == nil { |
| 109 | + t.Errorf("expected error when using an invalid CAFile, got nil") |
| 110 | + } |
| 111 | + }) |
| 112 | +} |
0 commit comments