Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[TT-13186/TT-13199] implement upstream basic authentication #6596

Merged
merged 16 commits into from
Oct 9, 2024
Merged
29 changes: 29 additions & 0 deletions apidef/api_definitions.go
Original file line number Diff line number Diff line change
Expand Up @@ -762,6 +762,35 @@ type APIDefinition struct {
VersionName string `bson:"-" json:"-"`

DetailedTracing bool `bson:"detailed_tracing" json:"detailed_tracing"`

// UpstreamAuth stores information about authenticating against upstream.
UpstreamAuth UpstreamAuth `bson:"upstream_auth" json:"upstream_auth"`
}

// UpstreamAuth holds the configurations related to upstream API authentication.
type UpstreamAuth struct {
// Enabled enables upstream API authentication.
Enabled bool `bson:"enabled" json:"enabled"`
// BasicAuth holds the basic authentication configuration for upstream API authentication.
BasicAuth UpstreamBasicAuth `bson:"basic_auth" json:"basic_auth"`
}

// IsEnabled checks if UpstreamAuthentication is enabled for the API.
func (u *UpstreamAuth) IsEnabled() bool {
return u.Enabled && u.BasicAuth.Enabled
}

// UpstreamBasicAuth holds upstream basic authentication configuration.
type UpstreamBasicAuth struct {
// Enabled enables upstream basic authentication.
Enabled bool `bson:"enabled" json:"enabled,omitempty"`
// Username is the username to be used for upstream basic authentication.
Username string `bson:"username" json:"username"`
// Password is the password to be used for upstream basic authentication.
Password string `bson:"password" json:"password"`
// HeaderName is the custom header name to be used for upstream basic authentication.
// Defaults to `Authorization`.
HeaderName string `bson:"header_name" json:"header_name"`
}

type AnalyticsPluginConfig struct {
Expand Down
37 changes: 37 additions & 0 deletions apidef/oas/schema/x-tyk-api-gateway.json
Original file line number Diff line number Diff line change
Expand Up @@ -1349,6 +1349,9 @@
},
"rateLimit": {
"$ref": "#/definitions/X-Tyk-RateLimit"
},
"authentication": {
"$ref": "#/definitions/X-Tyk-UpstreamAuthentication"
}
},
"required": [
Expand Down Expand Up @@ -2065,6 +2068,40 @@
"X-Tyk-DomainDef": {
"type": "string",
"pattern": "^([*a-zA-Z0-9-]+(\\.[*a-zA-Z0-9-]+)*)(:\\d+)?$"
},
"X-Tyk-UpstreamAuthentication": {
"type": "object",
"properties": {
"enabled": {
"type": "boolean"
},
"basicAuth": {
"$ref": "#/definitions/X-Tyk-UpstreamBasicAuthentication"
}
},
"required": [
"enabled"
]
},
"X-Tyk-UpstreamBasicAuthentication": {
"type": "object",
"properties": {
"enabled": {
"type": "boolean"
},
"headerName": {
"type": "string"
},
"username": {
"type": "string"
},
"password": {
"type": "string"
}
},
"required": [
"enabled"
]
}
}
}
87 changes: 87 additions & 0 deletions apidef/oas/upstream.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ type Upstream struct {

// RateLimit contains the configuration related to API level rate limit.
RateLimit *RateLimit `bson:"rateLimit,omitempty" json:"rateLimit,omitempty"`

// Authentication contains the configuration related to upstream authentication.
Authentication *UpstreamAuth `bson:"authentication,omitempty" json:"authentication,omitempty"`
}

// Fill fills *Upstream from apidef.APIDefinition.
Expand Down Expand Up @@ -79,6 +82,15 @@ func (u *Upstream) Fill(api apidef.APIDefinition) {
if ShouldOmit(u.RateLimit) {
u.RateLimit = nil
}

if u.Authentication == nil {
u.Authentication = &UpstreamAuth{}
}

u.Authentication.Fill(api.UpstreamAuth)
if ShouldOmit(u.Authentication) {
u.Authentication = nil
}
}

// ExtractTo extracts *Upstream into *apidef.APIDefinition.
Expand Down Expand Up @@ -129,6 +141,15 @@ func (u *Upstream) ExtractTo(api *apidef.APIDefinition) {
}

u.RateLimit.ExtractTo(api)

if u.Authentication == nil {
u.Authentication = &UpstreamAuth{}
defer func() {
u.Authentication = nil
}()
}

u.Authentication.ExtractTo(&api.UpstreamAuth)
}

// ServiceDiscovery holds configuration required for service discovery.
Expand Down Expand Up @@ -529,3 +550,69 @@ func (r *RateLimitEndpoint) ExtractTo(meta *apidef.RateLimitMeta) {
meta.Rate = float64(r.Rate)
meta.Per = r.Per.Seconds()
}

// UpstreamAuth holds the configurations related to upstream API authentication.
type UpstreamAuth struct {
// Enabled enables upstream API authentication.
Enabled bool `bson:"enabled" json:"enabled"`
// BasicAuth holds the basic authentication configuration for upstream API authentication.
BasicAuth *UpstreamBasicAuth `bson:"basicAuth,omitempty" json:"basicAuth,omitempty"`
}

// Fill fills *UpstreamAuth from apidef.UpstreamAuth.
func (u *UpstreamAuth) Fill(api apidef.UpstreamAuth) {
u.Enabled = api.Enabled

if u.BasicAuth == nil {
u.BasicAuth = &UpstreamBasicAuth{}
}

u.BasicAuth.Fill(api.BasicAuth)
if ShouldOmit(u.BasicAuth) {
u.BasicAuth = nil
}
}

// ExtractTo extracts *UpstreamAuth into *apidef.UpstreamAuth.
func (u *UpstreamAuth) ExtractTo(api *apidef.UpstreamAuth) {
api.Enabled = u.Enabled

if u.BasicAuth == nil {
u.BasicAuth = &UpstreamBasicAuth{}
defer func() {
u.BasicAuth = nil
}()
}

u.BasicAuth.ExtractTo(&api.BasicAuth)
}

// UpstreamBasicAuth holds upstream basic authentication configuration.
type UpstreamBasicAuth struct {
// Enabled enables upstream basic authentication.
Enabled bool `bson:"enabled" json:"enabled"`
// HeaderName is the custom header name to be used for upstream basic authentication.
// Defaults to `Authorization`.
HeaderName string `bson:"headerName" json:"headerName"`
// Username is the username to be used for upstream basic authentication.
Username string `bson:"username" json:"username"`
// Password is the password to be used for upstream basic authentication.
Password string `bson:"password" json:"password"`
}

// Fill fills *UpstreamBasicAuth from apidef.UpstreamBasicAuth.
func (u *UpstreamBasicAuth) Fill(api apidef.UpstreamBasicAuth) {
u.Enabled = api.Enabled
u.HeaderName = api.HeaderName
u.Username = api.Username
u.Password = api.Password
}

// ExtractTo extracts *UpstreamBasicAuth into *apidef.UpstreamBasicAuth.
func (u *UpstreamBasicAuth) ExtractTo(api *apidef.UpstreamBasicAuth) {
api.Enabled = u.Enabled
api.Enabled = u.Enabled
api.HeaderName = u.HeaderName
api.Username = u.Username
api.Password = u.Password
}
27 changes: 26 additions & 1 deletion apidef/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -761,7 +761,32 @@ const Schema = `{
},
"detailed_tracing": {
"type": "boolean"
}
},
"upstream_auth": {
"type": "object",
"properties": {
"enabled": {
"type": "boolean"
},
"basic_auth": {
"type": "object",
"properties": {
"enabled": {
"type": "boolean"
},
"username": {
"type": "string"
},
"password": {
"type": "string"
},
"header_name": {
"type": "string"
}
}
}
}
}
},
"required": [
"name",
Expand Down
11 changes: 4 additions & 7 deletions ctx/ctx.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import (
"encoding/json"
"net/http"

"github.com/TykTechnologies/tyk/internal/httputil"

"github.com/TykTechnologies/tyk/apidef/oas"

"github.com/TykTechnologies/tyk/config"
Expand Down Expand Up @@ -53,11 +55,6 @@ const (
OASDefinition
)

func setContext(r *http.Request, ctx context.Context) {
r2 := r.WithContext(ctx)
*r = *r2
}

func ctxSetSession(r *http.Request, s *user.SessionState, scheduleUpdate bool, hashKey bool) {

if s == nil {
Expand All @@ -81,7 +78,7 @@ func ctxSetSession(r *http.Request, s *user.SessionState, scheduleUpdate bool, h
s.Touch()
}

setContext(r, ctx)
httputil.SetContext(r, ctx)
}

func GetAuthToken(r *http.Request) string {
Expand Down Expand Up @@ -119,7 +116,7 @@ func SetSession(r *http.Request, s *user.SessionState, scheduleUpdate bool, hash
func SetDefinition(r *http.Request, s *apidef.APIDefinition) {
ctx := r.Context()
ctx = context.WithValue(ctx, Definition, s)
setContext(r, ctx)
httputil.SetContext(r, ctx)
}

func GetDefinition(r *http.Request) *apidef.APIDefinition {
Expand Down
2 changes: 2 additions & 0 deletions gateway/api_loader.go
Original file line number Diff line number Diff line change
Expand Up @@ -468,6 +468,8 @@ func (gw *Gateway) processSpec(spec *APISpec, apisByListen map[string]int,
}
}

gw.mwAppendEnabled(&chainArray, &UpstreamBasicAuth{BaseMiddleware: baseMid})

chain = alice.New(chainArray...).Then(&DummyProxyHandler{SH: SuccessHandler{baseMid}, Gw: gw})

if !spec.UseKeylessAccess {
Expand Down
61 changes: 61 additions & 0 deletions gateway/mw_upstream_basic_auth.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package gateway

import (
"net/http"

"github.com/TykTechnologies/tyk/internal/httputil"

"github.com/TykTechnologies/tyk/header"
)

// UpstreamBasicAuth is a middleware that will do basic authentication for upstream connections.
// UpstreamBasicAuth middleware is only supported in Tyk OAS API definitions.
type UpstreamBasicAuth struct {
*BaseMiddleware
}

// Name returns the name of middleware.
func (t *UpstreamBasicAuth) Name() string {
return "UpstreamBasicAuth"
}

// EnabledForSpec returns true if the middleware is enabled based on API Spec.
func (t *UpstreamBasicAuth) EnabledForSpec() bool {
if !t.Spec.UpstreamAuth.Enabled {
return false
}

if !t.Spec.UpstreamAuth.BasicAuth.Enabled {
return false
}

return true
}

// ProcessRequest will inject basic auth info into request context so that it can be used during reverse proxy.
func (t *UpstreamBasicAuth) ProcessRequest(_ http.ResponseWriter, r *http.Request, _ interface{}) (error, int) {
basicAuthConfig := t.Spec.UpstreamAuth.BasicAuth

upstreamBasicAuthProvider := UpstreamBasicAuthProvider{
HeaderName: header.Authorization,
}

if basicAuthConfig.HeaderName != "" {
upstreamBasicAuthProvider.HeaderName = basicAuthConfig.HeaderName
}

upstreamBasicAuthProvider.AuthValue = httputil.AuthHeader(basicAuthConfig.Username, basicAuthConfig.Password)

httputil.SetUpstreamAuth(r, upstreamBasicAuthProvider)
return nil, http.StatusOK
}

// UpstreamBasicAuthProvider implements auth provider
type UpstreamBasicAuthProvider struct {
HeaderName string
AuthValue string
}

func (u UpstreamBasicAuthProvider) Fill(r *http.Request) {
r.Header.Add(u.HeaderName, u.AuthValue)
}
Loading
Loading