Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/contracts/host_token_provider.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package contracts

type IHostTokenProvider interface {
ITokenProvider
GetTokenForHost(host string) (string, error)
}
43 changes: 39 additions & 4 deletions src/handler/proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"aad-auth-proxy/constants"
"aad-auth-proxy/contracts"
"aad-auth-proxy/utils"
"aad-auth-proxy/routing"
"bytes"
"context"
"errors"
Expand All @@ -13,6 +14,7 @@ import (
"net/http/httputil"
"net/url"
"strconv"
"strings"

log "github.com/sirupsen/logrus"
"go.opentelemetry.io/otel"
Expand All @@ -22,24 +24,44 @@ 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

return proxy, nil
}

// 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()

Expand All @@ -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{
Expand All @@ -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
Expand Down Expand Up @@ -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)
}


10 changes: 6 additions & 4 deletions src/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
87 changes: 87 additions & 0 deletions src/token_provider/host_token_provider.go
Original file line number Diff line number Diff line change
@@ -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)
}
14 changes: 14 additions & 0 deletions src/utils/configuration.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ type IConfiguration interface {
GetOtelEndpoint() string
GetOtelServiceName() string
GetAdditionalHeaders() map[string]string
GetHostAudienceMap() map[string]string
}

type configuration struct {
Expand Down Expand Up @@ -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
}