Files
zot/pkg/api/bearer_oidc_test.go
T
Matheus Pimenta bf619c570e Introduce support for OIDC workload identity federation (#3711)
* feat(oidc): introduce support for OIDC workload identity federation

Signed-off-by: Matheus Pimenta <matheuscscp@gmail.com>

* feat(oidc): add e2e test for bearer OIDC and a kind cluster

Signed-off-by: Matheus Pimenta <matheuscscp@gmail.com>

* feat(oidc): make OIDC workload identity federation its own feature

Signed-off-by: Matheus Pimenta <matheuscscp@gmail.com>

* feat(oidc): move errors to the errors package

Signed-off-by: Matheus Pimenta <matheuscscp@gmail.com>

* feat(oidc): fix race in cel package

Signed-off-by: Matheus Pimenta <matheuscscp@gmail.com>

* feat(oidc): compile cel expressions

Signed-off-by: Matheus Pimenta <matheuscscp@gmail.com>

---------

Signed-off-by: Matheus Pimenta <matheuscscp@gmail.com>
2026-01-24 21:03:53 -08:00

835 lines
23 KiB
Go

package api_test
import (
"context"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"crypto/x509/pkix"
"encoding/json"
"encoding/pem"
"errors"
"maps"
"math/big"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"testing"
"time"
"github.com/go-jose/go-jose/v4"
"github.com/golang-jwt/jwt/v5"
. "github.com/smartystreets/goconvey/convey"
"zotregistry.dev/zot/v2/pkg/api"
"zotregistry.dev/zot/v2/pkg/api/config"
"zotregistry.dev/zot/v2/pkg/log"
)
// mockOIDCServer creates a mock OIDC provider server for testing.
func mockOIDCServer(t *testing.T, pubKey *rsa.PublicKey) *httptest.Server {
t.Helper()
mux := http.NewServeMux()
// OpenID configuration endpoint
mux.HandleFunc("/.well-known/openid-configuration", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
config := map[string]any{
"issuer": "http://" + r.Host,
"jwks_uri": "http://" + r.Host + "/jwks",
}
_ = json.NewEncoder(w).Encode(config)
})
// JWKS endpoint
mux.HandleFunc("/jwks", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
// Create JWK from public key
jwk := jose.JSONWebKey{
Key: pubKey,
KeyID: "test-key-id",
Algorithm: string(jose.RS256),
Use: "sig",
}
jwks := map[string]any{
"keys": []jose.JSONWebKey{jwk},
}
_ = json.NewEncoder(w).Encode(jwks)
})
return httptest.NewServer(mux)
}
// createTestOIDCToken creates a test OIDC ID token.
func createTestOIDCToken(privKey *rsa.PrivateKey, issuer, audience, subject string,
claims map[string]any,
) (string, error) {
now := time.Now()
tokenClaims := jwt.MapClaims{
"iss": issuer,
"aud": []string{audience}, // Must be a slice for CEL processing
"sub": subject,
"exp": now.Add(time.Hour).Unix(),
"iat": now.Unix(),
}
// Add additional claims
maps.Copy(tokenClaims, claims)
token := jwt.NewWithClaims(jwt.SigningMethodRS256, tokenClaims)
token.Header["kid"] = "test-key-id"
return token.SignedString(privKey)
}
func TestOIDCBearerAuthorizer(t *testing.T) {
Convey("Test OIDC bearer token authorization", t, func() {
// Generate test keys
privKey, err := rsa.GenerateKey(rand.Reader, 2048)
So(err, ShouldBeNil)
pubKey := &privKey.PublicKey
// Start mock OIDC server
server := mockOIDCServer(t, pubKey)
defer server.Close()
issuer := server.URL
audience := "test-zot"
logger := log.NewLogger("debug", "")
Convey("Configuration validation", func() {
Convey("Empty config slice creates authorizer with no providers", func() {
authorizer, err := api.NewOIDCBearerAuthorizer([]config.BearerOIDCConfig{}, logger)
So(err, ShouldBeNil)
So(authorizer, ShouldNotBeNil)
// But authentication will always fail
result, err := authorizer.Authenticate(context.Background(), "Bearer token")
So(err, ShouldNotBeNil)
So(result, ShouldBeNil)
})
Convey("Empty issuer should fail", func() {
cfg := []config.BearerOIDCConfig{{
Audiences: []string{audience},
}}
_, err := api.NewOIDCBearerAuthorizer(cfg, logger)
So(err, ShouldNotBeNil)
})
Convey("Empty audiences should fail", func() {
cfg := []config.BearerOIDCConfig{{
Issuer: issuer,
Audiences: []string{},
}}
_, err := api.NewOIDCBearerAuthorizer(cfg, logger)
So(err, ShouldNotBeNil)
})
Convey("Valid config should succeed", func() {
cfg := []config.BearerOIDCConfig{{
Issuer: issuer,
Audiences: []string{audience},
}}
authorizer, err := api.NewOIDCBearerAuthorizer(cfg, logger)
So(err, ShouldBeNil)
So(authorizer, ShouldNotBeNil)
})
})
Convey("Token authentication", func() {
cfg := []config.BearerOIDCConfig{{
Issuer: issuer,
Audiences: []string{audience},
}}
ctx := context.Background()
authorizer, err := api.NewOIDCBearerAuthorizer(cfg, logger)
So(err, ShouldBeNil)
Convey("Empty header should fail", func() {
result, err := authorizer.Authenticate(ctx, "")
So(err, ShouldNotBeNil)
So(result, ShouldBeNil)
})
Convey("Invalid token format should fail", func() {
result, err := authorizer.Authenticate(ctx, "Bearer invalid-token")
So(err, ShouldNotBeNil)
So(result, ShouldBeNil)
})
Convey("Valid token with default claims", func() {
subject := "test-user" //nolint:goconst
token, err := createTestOIDCToken(privKey, issuer, audience, subject, nil)
So(err, ShouldBeNil)
authHeader := "Bearer " + token
result, err := authorizer.Authenticate(ctx, authHeader)
So(err, ShouldBeNil)
So(result, ShouldNotBeNil)
So(result.Username, ShouldEqual, issuer+"/"+subject)
So(result.Groups, ShouldBeEmpty)
})
Convey("Valid token with groups", func() {
subject := "test-user"
testGroups := []string{"group1", "group2"}
cfg := []config.BearerOIDCConfig{{
Issuer: issuer,
Audiences: []string{audience},
ClaimMapping: &config.CELClaimValidationAndMapping{
Groups: "claims.groups",
},
}}
authorizer, err := api.NewOIDCBearerAuthorizer(cfg, logger)
So(err, ShouldBeNil)
token, err := createTestOIDCToken(privKey, issuer, audience, subject, map[string]any{
"groups": testGroups,
})
So(err, ShouldBeNil)
authHeader := "Bearer " + token
result, err := authorizer.Authenticate(ctx, authHeader)
So(err, ShouldBeNil)
So(result, ShouldNotBeNil)
So(result.Username, ShouldEqual, issuer+"/"+subject)
So(result.Groups, ShouldResemble, testGroups)
})
Convey("Token with wrong audience should fail", func() {
subject := "test-user"
token, err := createTestOIDCToken(privKey, issuer, "wrong-audience", subject, nil)
So(err, ShouldBeNil)
authHeader := "Bearer " + token
result, err := authorizer.Authenticate(ctx, authHeader)
So(err, ShouldNotBeNil)
So(result, ShouldBeNil)
})
Convey("Expired token should fail", func() {
now := time.Now()
subject := "test-user"
tokenClaims := jwt.MapClaims{
"iss": issuer,
"aud": []string{audience},
"sub": subject,
"exp": now.Add(-time.Hour).Unix(), // Expired
"iat": now.Add(-2 * time.Hour).Unix(),
}
token := jwt.NewWithClaims(jwt.SigningMethodRS256, tokenClaims)
token.Header["kid"] = "test-key-id"
tokenString, err := token.SignedString(privKey)
So(err, ShouldBeNil)
authHeader := "Bearer " + tokenString
result, err := authorizer.Authenticate(ctx, authHeader)
So(err, ShouldNotBeNil)
So(result, ShouldBeNil)
})
})
Convey("Custom claim mapping", func() {
customClaimName := "preferred_username"
customUsername := "custom-user"
cfg := []config.BearerOIDCConfig{{
Issuer: issuer,
Audiences: []string{audience},
ClaimMapping: &config.CELClaimValidationAndMapping{
Username: "claims.preferred_username",
},
}}
ctx := context.Background()
authorizer, err := api.NewOIDCBearerAuthorizer(cfg, logger)
So(err, ShouldBeNil)
Convey("Extract username from custom claim", func() {
subject := "original-sub"
token, err := createTestOIDCToken(privKey, issuer, audience, subject, map[string]any{
customClaimName: customUsername,
})
So(err, ShouldBeNil)
authHeader := "Bearer " + token
result, err := authorizer.Authenticate(ctx, authHeader)
So(err, ShouldBeNil)
So(result, ShouldNotBeNil)
So(result.Username, ShouldEqual, customUsername)
So(result.Groups, ShouldBeEmpty)
})
Convey("Error when custom claim missing (no fallback)", func() {
subject := "fallback-user"
token, err := createTestOIDCToken(privKey, issuer, audience, subject, nil)
So(err, ShouldBeNil)
authHeader := "Bearer " + token
// With CEL expressions, missing claims cause an error (no automatic fallback)
result, err := authorizer.Authenticate(ctx, authHeader)
So(err, ShouldNotBeNil)
So(result, ShouldBeNil)
})
})
Convey("Multiple audiences", func() {
audiences := []string{"audience1", "audience2", "audience3"}
cfg := []config.BearerOIDCConfig{{
Issuer: issuer,
Audiences: audiences,
}}
ctx := context.Background()
authorizer, err := api.NewOIDCBearerAuthorizer(cfg, logger)
So(err, ShouldBeNil)
Convey("Token with first audience should work", func() {
subject := "test-user"
token, err := createTestOIDCToken(privKey, issuer, audiences[0], subject, nil)
So(err, ShouldBeNil)
authHeader := "Bearer " + token
result, err := authorizer.Authenticate(ctx, authHeader)
So(err, ShouldBeNil)
So(result, ShouldNotBeNil)
So(result.Username, ShouldEqual, issuer+"/"+subject)
So(result.Groups, ShouldBeEmpty)
})
Convey("Token with second audience should work", func() {
subject := "test-user"
token, err := createTestOIDCToken(privKey, issuer, audiences[1], subject, nil)
So(err, ShouldBeNil)
authHeader := "Bearer " + token
result, err := authorizer.Authenticate(ctx, authHeader)
So(err, ShouldBeNil)
So(result, ShouldNotBeNil)
So(result.Username, ShouldEqual, issuer+"/"+subject)
So(result.Groups, ShouldBeEmpty)
})
})
Convey("Multiple OIDC providers", func() {
ctx := context.Background()
// Create a second mock server with a different key
privKey2, err := rsa.GenerateKey(rand.Reader, 2048)
So(err, ShouldBeNil)
pubKey2 := &privKey2.PublicKey
server2 := mockOIDCServer(t, pubKey2)
defer server2.Close()
issuer2 := server2.URL
audience2 := "test-zot-2"
Convey("Token valid for second provider succeeds", func() {
// Configure two providers - token is only valid for the second one
cfg := []config.BearerOIDCConfig{
{
Issuer: issuer,
Audiences: []string{audience},
},
{
Issuer: issuer2,
Audiences: []string{audience2},
},
}
authorizer, err := api.NewOIDCBearerAuthorizer(cfg, logger)
So(err, ShouldBeNil)
// Create token for second provider
subject := "test-user"
token, err := createTestOIDCToken(privKey2, issuer2, audience2, subject, nil)
So(err, ShouldBeNil)
authHeader := "Bearer " + token
result, err := authorizer.Authenticate(ctx, authHeader)
So(err, ShouldBeNil)
So(result, ShouldNotBeNil)
So(result.Username, ShouldEqual, issuer2+"/"+subject)
})
Convey("Token valid for first provider succeeds immediately", func() {
cfg := []config.BearerOIDCConfig{
{
Issuer: issuer,
Audiences: []string{audience},
},
{
Issuer: issuer2,
Audiences: []string{audience2},
},
}
authorizer, err := api.NewOIDCBearerAuthorizer(cfg, logger)
So(err, ShouldBeNil)
// Create token for first provider
subject := "test-user"
token, err := createTestOIDCToken(privKey, issuer, audience, subject, nil)
So(err, ShouldBeNil)
authHeader := "Bearer " + token
result, err := authorizer.Authenticate(ctx, authHeader)
So(err, ShouldBeNil)
So(result, ShouldNotBeNil)
So(result.Username, ShouldEqual, issuer+"/"+subject)
})
Convey("Token invalid for all providers returns aggregated errors", func() {
cfg := []config.BearerOIDCConfig{
{
Issuer: issuer,
Audiences: []string{audience},
},
{
Issuer: issuer2,
Audiences: []string{audience2},
},
}
authorizer, err := api.NewOIDCBearerAuthorizer(cfg, logger)
So(err, ShouldBeNil)
// Create token with wrong audience for both providers
subject := "test-user"
token, err := createTestOIDCToken(privKey, issuer, "wrong-audience", subject, nil)
So(err, ShouldBeNil)
authHeader := "Bearer " + token
result, err := authorizer.Authenticate(ctx, authHeader)
So(err, ShouldNotBeNil)
So(result, ShouldBeNil)
// Error should contain information from both providers
So(err.Error(), ShouldContainSubstring, "invalid bearer token")
})
})
Convey("AuthenticateRequest convenience method", func() {
cfg := []config.BearerOIDCConfig{{
Issuer: issuer,
Audiences: []string{audience},
}}
ctx := context.Background()
authorizer, err := api.NewOIDCBearerAuthorizer(cfg, logger)
So(err, ShouldBeNil)
Convey("Successful authentication returns username, groups, and true", func() {
subject := "test-user"
token, err := createTestOIDCToken(privKey, issuer, audience, subject, nil)
So(err, ShouldBeNil)
authHeader := "Bearer " + token
username, groups, ok, err := authorizer.AuthenticateRequest(ctx, authHeader)
So(err, ShouldBeNil)
So(ok, ShouldBeTrue)
So(username, ShouldEqual, issuer+"/"+subject)
So(groups, ShouldBeEmpty)
})
Convey("Failed authentication returns false and error", func() {
result, groups, ok, err := authorizer.AuthenticateRequest(ctx, "Bearer invalid-token")
So(err, ShouldNotBeNil)
So(ok, ShouldBeFalse)
So(result, ShouldBeEmpty)
So(groups, ShouldBeEmpty)
})
})
Convey("CEL validations", func() {
ctx := context.Background()
Convey("Validation passes when expression is true", func() {
cfg := []config.BearerOIDCConfig{{
Issuer: issuer,
Audiences: []string{audience},
ClaimMapping: &config.CELClaimValidationAndMapping{
Validations: []config.CELValidation{
{
Expression: "claims.email_verified == true",
Message: "email must be verified",
},
},
},
}}
authorizer, err := api.NewOIDCBearerAuthorizer(cfg, logger)
So(err, ShouldBeNil)
subject := "test-user"
token, err := createTestOIDCToken(privKey, issuer, audience, subject, map[string]any{
"email_verified": true,
})
So(err, ShouldBeNil)
authHeader := "Bearer " + token
result, err := authorizer.Authenticate(ctx, authHeader)
So(err, ShouldBeNil)
So(result, ShouldNotBeNil)
So(result.Username, ShouldEqual, issuer+"/"+subject)
})
Convey("Validation fails when expression is false", func() {
cfg := []config.BearerOIDCConfig{{
Issuer: issuer,
Audiences: []string{audience},
ClaimMapping: &config.CELClaimValidationAndMapping{
Validations: []config.CELValidation{
{
Expression: "claims.email_verified == true",
Message: "email must be verified",
},
},
},
}}
authorizer, err := api.NewOIDCBearerAuthorizer(cfg, logger)
So(err, ShouldBeNil)
subject := "test-user"
token, err := createTestOIDCToken(privKey, issuer, audience, subject, map[string]any{
"email_verified": false,
})
So(err, ShouldBeNil)
authHeader := "Bearer " + token
result, err := authorizer.Authenticate(ctx, authHeader)
So(err, ShouldNotBeNil)
So(result, ShouldBeNil)
So(err.Error(), ShouldContainSubstring, "email must be verified")
})
Convey("CEL variables can be used in validations and username", func() {
cfg := []config.BearerOIDCConfig{{
Issuer: issuer,
Audiences: []string{audience},
ClaimMapping: &config.CELClaimValidationAndMapping{
Variables: []config.CELVariable{
{
Name: "org",
Expression: "claims.organization",
},
},
Validations: []config.CELValidation{
{
Expression: "vars.org in ['allowed-org', 'another-org']",
Message: "organization not allowed",
},
},
Username: "vars.org + '/' + claims.sub",
},
}}
authorizer, err := api.NewOIDCBearerAuthorizer(cfg, logger)
So(err, ShouldBeNil)
subject := "test-user"
token, err := createTestOIDCToken(privKey, issuer, audience, subject, map[string]any{
"organization": "allowed-org",
})
So(err, ShouldBeNil)
authHeader := "Bearer " + token
result, err := authorizer.Authenticate(ctx, authHeader)
So(err, ShouldBeNil)
So(result, ShouldNotBeNil)
So(result.Username, ShouldEqual, "allowed-org/test-user")
})
})
})
}
func TestBearerOIDCConfig(t *testing.T) {
Convey("Test Bearer OIDC configuration", t, func() {
Convey("IsBearerAuthEnabled with OIDC config", func() {
authConfig := &config.AuthConfig{
Bearer: &config.BearerConfig{
OIDC: []config.BearerOIDCConfig{{
Issuer: "https://issuer.example.com",
Audiences: []string{"zot"},
}},
},
}
So(authConfig.IsBearerAuthEnabled(), ShouldBeTrue)
So(authConfig.IsOIDCBearerAuthEnabled(), ShouldBeTrue)
So(authConfig.IsTraditionalBearerAuthEnabled(), ShouldBeFalse)
})
Convey("IsBearerAuthEnabled with traditional bearer", func() {
authConfig := &config.AuthConfig{
Bearer: &config.BearerConfig{
Realm: "zot",
Service: "zot-service",
Cert: "/path/to/cert",
},
}
So(authConfig.IsBearerAuthEnabled(), ShouldBeTrue)
So(authConfig.IsTraditionalBearerAuthEnabled(), ShouldBeTrue)
So(authConfig.IsOIDCBearerAuthEnabled(), ShouldBeFalse)
})
Convey("IsBearerAuthEnabled with both", func() {
authConfig := &config.AuthConfig{
Bearer: &config.BearerConfig{
Realm: "zot",
Service: "zot-service",
Cert: "/path/to/cert",
OIDC: []config.BearerOIDCConfig{{
Issuer: "https://issuer.example.com",
Audiences: []string{"zot"},
}},
},
}
So(authConfig.IsBearerAuthEnabled(), ShouldBeTrue)
So(authConfig.IsTraditionalBearerAuthEnabled(), ShouldBeTrue)
So(authConfig.IsOIDCBearerAuthEnabled(), ShouldBeTrue)
})
Convey("IsBearerAuthEnabled without proper config", func() {
authConfig := &config.AuthConfig{
Bearer: &config.BearerConfig{
OIDC: []config.BearerOIDCConfig{{
Issuer: "https://issuer.example.com",
// Missing audiences
}},
},
}
So(authConfig.IsBearerAuthEnabled(), ShouldBeFalse)
So(authConfig.IsOIDCBearerAuthEnabled(), ShouldBeFalse)
So(authConfig.IsTraditionalBearerAuthEnabled(), ShouldBeFalse)
})
Convey("IsBearerAuthEnabled with nil bearer", func() {
authConfig := &config.AuthConfig{
Bearer: nil,
}
So(authConfig.IsBearerAuthEnabled(), ShouldBeFalse)
So(authConfig.IsOIDCBearerAuthEnabled(), ShouldBeFalse)
So(authConfig.IsTraditionalBearerAuthEnabled(), ShouldBeFalse)
})
Convey("IsOIDCBearerAuthEnabled with nil AuthConfig", func() {
var authConfig *config.AuthConfig = nil
So(authConfig.IsOIDCBearerAuthEnabled(), ShouldBeFalse)
})
Convey("IsTraditionalBearerAuthEnabled with nil AuthConfig", func() {
var authConfig *config.AuthConfig = nil
So(authConfig.IsTraditionalBearerAuthEnabled(), ShouldBeFalse)
})
Convey("IsTraditionalBearerAuthEnabled with partial config", func() {
// Missing Cert
authConfig := &config.AuthConfig{
Bearer: &config.BearerConfig{
Realm: "zot",
Service: "zot-service",
},
}
So(authConfig.IsTraditionalBearerAuthEnabled(), ShouldBeFalse)
// Missing Realm
authConfig = &config.AuthConfig{
Bearer: &config.BearerConfig{
Service: "zot-service",
Cert: "/path/to/cert",
},
}
So(authConfig.IsTraditionalBearerAuthEnabled(), ShouldBeFalse)
// Missing Service
authConfig = &config.AuthConfig{
Bearer: &config.BearerConfig{
Realm: "zot",
Cert: "/path/to/cert",
},
}
So(authConfig.IsTraditionalBearerAuthEnabled(), ShouldBeFalse)
})
})
}
func TestGetBearerOIDCTestHTTPClient(t *testing.T) {
Convey("Test GetBearerOIDCTestHTTPClient", t, func() {
// Save original env var and restore after test
originalEnv := os.Getenv("ZOT_BEARER_OIDC_TEST_CA_FILE")
Reset(func() {
os.Setenv("ZOT_BEARER_OIDC_TEST_CA_FILE", originalEnv)
})
Convey("Returns nil when env var is empty", func() {
os.Unsetenv("ZOT_BEARER_OIDC_TEST_CA_FILE")
client := api.GetBearerOIDCTestHTTPClient()
So(client, ShouldBeNil)
})
Convey("Returns nil when CA file does not exist", func() {
os.Setenv("ZOT_BEARER_OIDC_TEST_CA_FILE", "/nonexistent/path/to/ca.crt")
client := api.GetBearerOIDCTestHTTPClient()
So(client, ShouldBeNil)
})
Convey("Returns nil when CA file contains invalid PEM data", func() {
tmpDir := t.TempDir()
caFile := filepath.Join(tmpDir, "invalid-ca.crt")
err := os.WriteFile(caFile, []byte("not a valid PEM certificate"), 0o600)
So(err, ShouldBeNil)
os.Setenv("ZOT_BEARER_OIDC_TEST_CA_FILE", caFile)
client := api.GetBearerOIDCTestHTTPClient()
So(client, ShouldBeNil)
})
Convey("Returns nil when CA file contains valid PEM but not a certificate", func() {
tmpDir := t.TempDir()
caFile := filepath.Join(tmpDir, "not-a-cert.pem")
// Create a valid PEM block but with wrong type
pemBlock := &pem.Block{
Type: "PRIVATE KEY",
Bytes: []byte("fake key data"),
}
pemData := pem.EncodeToMemory(pemBlock)
err := os.WriteFile(caFile, pemData, 0o600)
So(err, ShouldBeNil)
os.Setenv("ZOT_BEARER_OIDC_TEST_CA_FILE", caFile)
client := api.GetBearerOIDCTestHTTPClient()
So(client, ShouldBeNil)
})
Convey("Returns configured HTTP client with valid CA file", func() {
tmpDir := t.TempDir()
caFile := filepath.Join(tmpDir, "ca.crt")
// Generate a self-signed CA certificate
caCert := createTestCACertificate(t)
err := os.WriteFile(caFile, caCert, 0o600)
So(err, ShouldBeNil)
os.Setenv("ZOT_BEARER_OIDC_TEST_CA_FILE", caFile)
client := api.GetBearerOIDCTestHTTPClient()
So(client, ShouldNotBeNil)
So(client.Transport, ShouldNotBeNil)
})
Convey("Returns nil when http.DefaultTransport is not *http.Transport", func() {
tmpDir := t.TempDir()
caFile := filepath.Join(tmpDir, "ca.crt")
// Generate a valid CA certificate
caCert := createTestCACertificate(t)
err := os.WriteFile(caFile, caCert, 0o600)
So(err, ShouldBeNil)
os.Setenv("ZOT_BEARER_OIDC_TEST_CA_FILE", caFile)
// Save the original DefaultTransport and restore after test
originalTransport := http.DefaultTransport
defer func() {
http.DefaultTransport = originalTransport
}()
// Replace with a custom RoundTripper that is not *http.Transport
http.DefaultTransport = &customRoundTripper{}
client := api.GetBearerOIDCTestHTTPClient()
So(client, ShouldBeNil)
})
})
}
// customRoundTripper is a mock RoundTripper that is not *http.Transport.
type customRoundTripper struct{}
var errNotImplemented = errors.New("not implemented")
func (c *customRoundTripper) RoundTrip(*http.Request) (*http.Response, error) {
return nil, errNotImplemented
}
// createTestCACertificate generates a self-signed CA certificate for testing.
func createTestCACertificate(t *testing.T) []byte {
t.Helper()
privKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
t.Fatalf("failed to generate private key: %v", err)
}
template := x509.Certificate{
SerialNumber: big.NewInt(1),
Subject: pkix.Name{
Organization: []string{"Test CA"},
},
NotBefore: time.Now(),
NotAfter: time.Now().Add(time.Hour),
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign,
BasicConstraintsValid: true,
IsCA: true,
}
certDER, err := x509.CreateCertificate(rand.Reader, &template, &template, &privKey.PublicKey, privKey)
if err != nil {
t.Fatalf("failed to create certificate: %v", err)
}
certPEM := pem.EncodeToMemory(&pem.Block{
Type: "CERTIFICATE",
Bytes: certDER,
})
return certPEM
}