diff --git a/src/contracts/host_token_provider.go b/src/contracts/host_token_provider.go new file mode 100644 index 0000000..5ed31bc --- /dev/null +++ b/src/contracts/host_token_provider.go @@ -0,0 +1,6 @@ +package contracts + +type IHostTokenProvider interface { + ITokenProvider + GetTokenForHost(host string) (string, error) +} \ No newline at end of file diff --git a/src/handler/proxy.go b/src/handler/proxy.go index 62b2af4..11dbb92 100644 --- a/src/handler/proxy.go +++ b/src/handler/proxy.go @@ -4,6 +4,7 @@ import ( "aad-auth-proxy/constants" "aad-auth-proxy/contracts" "aad-auth-proxy/utils" + "aad-auth-proxy/routing" "bytes" "context" "errors" @@ -13,6 +14,7 @@ import ( "net/http/httputil" "net/url" "strconv" + "strings" log "github.com/sirupsen/logrus" "go.opentelemetry.io/otel" @@ -22,16 +24,36 @@ import ( ) // Creates proxy for incoming requests -func CreateReverseProxy(targetHost string, tokenProvider contracts.ITokenProvider) (*httputil.ReverseProxy, error) { +func CreateReverseProxy(targetHost string, tokenProvider contracts.IHostTokenProvider) (*httputil.ReverseProxy, error) { url, err := url.Parse(targetHost) if err != nil { return nil, err } proxy := httputil.NewSingleHostReverseProxy(url) - proxy.Director = func(request *http.Request) { - modifyRequest(request, targetHost, tokenProvider) + proxy.Director = func(req *http.Request) { + host, err := routing.BuildTargetHost(req) + if err != nil { + req.URL = nil + return + } + + if err := modifyRequest(req, host, tokenProvider); err != nil { + req.URL = nil + return + } + + // strip first path segment (/app1/xyz → /xyz) + path := req.URL.Path + segments := strings.SplitN(path, "/", 3) + + if len(segments) >= 3 { + req.URL.Path = "/" + segments[2] + } else { + req.URL.Path = "/" + } } + proxy.ErrorHandler = handleError proxy.ModifyResponse = modifyResponse @@ -39,7 +61,7 @@ func CreateReverseProxy(targetHost string, tokenProvider contracts.ITokenProvide } // This modifies incoming requests and changes host to targetHost -func modifyRequest(request *http.Request, targetHost string, tokenProvider contracts.ITokenProvider) { +func modifyRequest(request *http.Request, targetHost string, tokenProvider contracts.IHostTokenProvider) error { ctx, span := otel.Tracer(constants.SERVICE_TELEMETRY_KEY).Start(request.Context(), "modifyRequest") defer span.End() @@ -52,6 +74,14 @@ func modifyRequest(request *http.Request, targetHost string, tokenProvider contr request.URL.Host = targetHost request.Host = targetHost + token, err := tokenProvider.GetTokenForHost(targetHost) + if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, "failed to get token for host") + return err + } + request.Header.Set(constants.HEADER_AUTHORIZATION, "Bearer "+token) + // Record metrics // request_bytes_total{target_host, method, path, user_agent} metricAttributes := []attribute.KeyValue{ @@ -66,6 +96,9 @@ func modifyRequest(request *http.Request, targetHost string, tokenProvider contr if err == nil { instrument.Add(ctx, request.ContentLength, metric.WithAttributes(metricAttributes...)) } + + // metrics, log, ecc. + return nil } // This will be called when there is an error in forwarding the request @@ -212,3 +245,5 @@ func logResponse(ctx context.Context, response *http.Response) { response.Header.Set(constants.HEADER_CONTENT_LENGTH, fmt.Sprint(buffer.Len())) response.Header.Set(constants.HEADER_CONTENT_ENCODING, encoding) } + + diff --git a/src/main.go b/src/main.go index 6b5015d..30025af 100644 --- a/src/main.go +++ b/src/main.go @@ -91,18 +91,20 @@ func createHandlerWithTokenProvider(configuration utils.IConfiguration, audience } } - // Create TokenProvider - tokenProvider, err := token_provider.NewTokenProvider(audience, configuration, certManager, logger) + hostAudience := configuration.GetHostAudienceMap() + + tokenProvider, err := token_provider.NewHostTokenProvider(audience, hostAudience, configuration, certManager, logger) if err != nil { - logger.Error("TokenCredential creation failed:", err) + logger.Error("HostTokenProvider creation failed:", err) + return nil } proxy, err := handler.CreateReverseProxy(targetHost, tokenProvider) if err != nil { logger.Error("Proxy creation failed:", err) + return nil } - // Create handler to return tokens based on audience handler, err := handler.NewHandler(proxy, tokenProvider, configuration) if err != nil { logger.Error("NewHandler failed:", err) diff --git a/src/token_provider/host_token_provider.go b/src/token_provider/host_token_provider.go new file mode 100644 index 0000000..6c299e7 --- /dev/null +++ b/src/token_provider/host_token_provider.go @@ -0,0 +1,87 @@ +// filepath: host_token_provider.go +package token_provider + +import ( + "aad-auth-proxy/certificate" + "aad-auth-proxy/contracts" + "aad-auth-proxy/utils" + "fmt" + "net" + "strings" +) + +type HostTokenProvider struct { + defaultProvider contracts.ITokenProvider + perHostProvider map[string]contracts.ITokenProvider +} + +func NewHostTokenProvider( + defaultAudience string, + hostAudience map[string]string, + configuration utils.IConfiguration, + certManager *certificate.CertificateManager, + logger contracts.ILogger, +) (*HostTokenProvider, error) { + defaultProvider, err := NewTokenProvider(defaultAudience, configuration, certManager, logger) + if err != nil { + return nil, err + } + + perHost := make(map[string]contracts.ITokenProvider, len(hostAudience)) + for h, aud := range hostAudience { + p, e := NewTokenProvider(aud, configuration, certManager, logger) + if e != nil { + return nil, e + } + perHost[normalizeHost(h)] = p + } + + return &HostTokenProvider{ + defaultProvider: defaultProvider, + perHostProvider: perHost, + }, nil +} + +// Compat: espone vari nomi possibili usati dal progetto. +func (h *HostTokenProvider) GetToken() (string, error) { + return getTokenFromProvider(h.defaultProvider) +} + +func (h *HostTokenProvider) GetClientToken() (string, error) { + return getTokenFromProvider(h.defaultProvider) +} + +func (h *HostTokenProvider) GetAccessToken() (string, error) { + return getTokenFromProvider(h.defaultProvider) +} + +func (h *HostTokenProvider) GetTokenForHost(host string) (string, error) { + n := normalizeHost(host) + if p, ok := h.perHostProvider[n]; ok { + return getTokenFromProvider(p) + } + return getTokenFromProvider(h.defaultProvider) +} + +func normalizeHost(host string) string { + host = strings.TrimSpace(strings.ToLower(host)) + onlyHost, _, err := net.SplitHostPort(host) + if err == nil { + return onlyHost + } + return host +} + +func getTokenFromProvider(p contracts.ITokenProvider) (string, error) { + // prova le firme più comuni + if tp, ok := any(p).(interface{ GetAccessToken() (string, error) }); ok { + return tp.GetAccessToken() + } + if tp, ok := any(p).(interface{ GetClientToken() (string, error) }); ok { + return tp.GetClientToken() + } + if tp, ok := any(p).(interface{ GetToken() (string, error) }); ok { + return tp.GetToken() + } + return "", fmt.Errorf("unsupported token getter on provider type %T", p) +} \ No newline at end of file diff --git a/src/utils/configuration.go b/src/utils/configuration.go index d1f73fa..43672fa 100644 --- a/src/utils/configuration.go +++ b/src/utils/configuration.go @@ -22,6 +22,7 @@ type IConfiguration interface { GetOtelEndpoint() string GetOtelServiceName() string GetAdditionalHeaders() map[string]string + GetHostAudienceMap() map[string]string } type configuration struct { @@ -232,3 +233,16 @@ func (config *configuration) GetOtelServiceName() string { func (config *configuration) GetAdditionalHeaders() map[string]string { return config.additionalHeaders.headers } + +func (c *configuration) GetHostAudienceMap() map[string]string { + raw := os.Getenv("HOST_AUDIENCE_MAP") + if raw == "" { + return map[string]string{} + } + + out := map[string]string{} + if err := json.Unmarshal([]byte(raw), &out); err != nil { + return map[string]string{} + } + return out +}