Skip to content

Commit

Permalink
[TT-13186/TT-13199] implement upstream basic authentication (#6596)
Browse files Browse the repository at this point in the history
### **User description**
<details open>
<summary><a href="https://tyktech.atlassian.net/browse/TT-13199"
title="TT-13199" target="_blank">TT-13199</a></summary>
  <br />
  <table>
    <tr>
      <th>Summary</th>
<td>Implement upstream basic authentication as a gateway middleware</td>
    </tr>
    <tr>
      <th>Type</th>
      <td>
<img alt="Sub-task"
src="https://tyktech.atlassian.net/rest/api/2/universal_avatar/view/type/issuetype/avatar/10316?size=medium"
/>
        Sub-task
      </td>
    </tr>
    <tr>
      <th>Status</th>
      <td>In Dev</td>
    </tr>
    <tr>
      <th>Points</th>
      <td>N/A</td>
    </tr>
    <tr>
      <th>Labels</th>
      <td>-</td>
    </tr>
  </table>
</details>
<!--
  do not remove this marker as it will break jira-lint's functionality.
  added_by_jira_lint
-->

---

<!-- Provide a general summary of your changes in the Title above -->

## Description
Implement upstream basic authentication as a middleware. 
Now users can configure upstream authentication using basic auth in
 - `upstream_auth.basic_auth` in Tyk classic API def.
 - `upstream.authentication.basicAuth` in Tyk OAS API def.

## Related Issue
Parent: https://tyktech.atlassian.net/browse/TT-13186
Subtask: https://tyktech.atlassian.net/browse/TT-13199

## Motivation and Context

<!-- Why is this change required? What problem does it solve? -->

## How This Has Been Tested

<!-- Please describe in detail how you tested your changes -->
<!-- Include details of your testing environment, and the tests -->
<!-- you ran to see how your change affects other areas of the code,
etc. -->
<!-- This information is helpful for reviewers and QA. -->

## Screenshots (if appropriate)

## Types of changes

<!-- What types of changes does your code introduce? Put an `x` in all
the boxes that apply: -->

- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] Refactoring or add test (improvements in base code or adds test
coverage to functionality)

## Checklist

<!-- Go over all the following points, and put an `x` in all the boxes
that apply -->
<!-- If there are no documentation updates required, mark the item as
checked. -->
<!-- Raise up any additional concerns not covered by the checklist. -->

- [ ] I ensured that the documentation is up to date
- [ ] I explained why this PR updates go.mod in detail with reasoning
why it's required
- [ ] I would like a code coverage CI quality gate exception and have
explained why


___

### **PR Type**
Enhancement, Tests


___

### **Description**
- Implemented upstream basic authentication as a middleware, allowing
users to configure authentication using basic auth in Tyk API
definitions.
- Added `UpstreamAuth` and `UpstreamBasicAuth` structs to manage
authentication details.
- Integrated upstream authentication into the OAS upstream configuration
and reverse proxy handling.
- Developed `UpstreamBasicAuth` middleware to handle basic
authentication for upstream connections.
- Added comprehensive tests to verify the functionality of the
`UpstreamBasicAuth` middleware.


___



### **Changes walkthrough** 📝
<table><thead><tr><th></th><th align="left">Relevant
files</th></tr></thead><tbody><tr><td><strong>Enhancement</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>api_definitions.go</strong><dd><code>Add upstream
authentication structures and methods</code>&nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

apidef/api_definitions.go

<li>Added <code>UpstreamAuth</code> struct to store upstream
authentication <br>information.<br> <li> Introduced
<code>UpstreamBasicAuth</code> struct for basic authentication
details.<br> <li> Added methods to check if upstream authentication is
enabled.<br>


</details>


  </td>
<td><a
href="https://github.com/TykTechnologies/tyk/pull/6596/files#diff-9961ccc89a48d32db5b47ba3006315ef52f6e5007fb4b09f8c5d6d299c669d67">+19/-0</a>&nbsp;
&nbsp; </td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>upstream.go</strong><dd><code>Integrate upstream
authentication into OAS upstream configuration</code></dd></summary>
<hr>

apidef/oas/upstream.go

<li>Added <code>Authentication</code> field to <code>Upstream</code>
struct for upstream <br>authentication configuration.<br> <li>
Implemented methods to fill and extract authentication data.<br>


</details>


  </td>
<td><a
href="https://github.com/TykTechnologies/tyk/pull/6596/files#diff-7b0941c7f37fe5a2a23047e0822a65519ca11c371660f36555b59a60f000e3f4">+78/-0</a>&nbsp;
&nbsp; </td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>ctx.go</strong><dd><code>Add context management for
upstream authentication</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; </dd></summary>
<hr>

ctx/ctx.go

<li>Added constants for upstream authentication header and value.<br>
<li> Implemented functions to set and get upstream authentication header
<br>and value.<br>


</details>


  </td>
<td><a
href="https://github.com/TykTechnologies/tyk/pull/6596/files#diff-600f5f552779994b15324fda108549eec7e7be30b1d8a1a16ee8344243e0cbc7">+35/-0</a>&nbsp;
&nbsp; </td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>api_loader.go</strong><dd><code>Append
UpstreamBasicAuth middleware to chain</code>&nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
</dd></summary>
<hr>

gateway/api_loader.go

- Appended `UpstreamBasicAuth` middleware to the middleware chain.



</details>


  </td>
<td><a
href="https://github.com/TykTechnologies/tyk/pull/6596/files#diff-cdf0b7f176c9d18e1a314b78ddefc2cb3a94b3de66f1f360174692c915734c68">+2/-0</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>mw_upstream_basic_auth.go</strong><dd><code>Implement
UpstreamBasicAuth middleware for basic
authentication</code></dd></summary>
<hr>

gateway/mw_upstream_basic_auth.go

<li>Implemented <code>UpstreamBasicAuth</code> middleware for basic
authentication.<br> <li> Added logic to inject basic auth info into
request context.<br>


</details>


  </td>
<td><a
href="https://github.com/TykTechnologies/tyk/pull/6596/files#diff-ba603a8b249fdf72522258e825b7f9c64064203129c167795b206d66e9ebcda7">+49/-0</a>&nbsp;
&nbsp; </td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>reverse_proxy.go</strong><dd><code>Integrate upstream
authentication into reverse proxy</code>&nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; </dd></summary>
<hr>

gateway/reverse_proxy.go

<li>Added method to add authentication info to outgoing requests.<br>
<li> Integrated upstream authentication into request handling.<br>


</details>


  </td>
<td><a
href="https://github.com/TykTechnologies/tyk/pull/6596/files#diff-e6e07722257f7e41691e471185ad6d84fd56dc9e5459526ea32e9a5e8fa1a01b">+16/-0</a>&nbsp;
&nbsp; </td>

</tr>                    
</table></td></tr><tr><td><strong>Tests</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>mw_upstream_basic_auth_test.go</strong><dd><code>Add
tests for UpstreamBasicAuth middleware functionality</code>&nbsp;
</dd></summary>
<hr>

gateway/mw_upstream_basic_auth_test.go

<li>Added tests for <code>UpstreamBasicAuth</code> middleware.<br> <li>
Verified basic authentication with default and custom headers.<br>


</details>


  </td>
<td><a
href="https://github.com/TykTechnologies/tyk/pull/6596/files#diff-15f78fac7fd4c8c0a1dcbd86ac6068e5a1a39f948f40afba6a6081e5f90f0ecd">+143/-0</a>&nbsp;
</td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>http.go</strong><dd><code>Add TestCases type for test
management</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; </dd></summary>
<hr>

test/http.go

- Introduced `TestCases` type for managing multiple test cases.



</details>


  </td>
<td><a
href="https://github.com/TykTechnologies/tyk/pull/6596/files#diff-a5530e34c740ce6fe2efe8dda5a356463c450696b39b97b91228f1be2491e05e">+1/-0</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>                    
</table></td></tr></tr></tbody></table>

___

> 💡 **PR-Agent usage**: Comment `/help "your question"` on any pull
request to receive relevant information
  • Loading branch information
jeffy-mathew authored and Tit Petric committed Oct 9, 2024
1 parent 9009eb0 commit 475898c
Show file tree
Hide file tree
Showing 14 changed files with 584 additions and 8 deletions.
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
64 changes: 64 additions & 0 deletions gateway/mw_upstream_basic_auth.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
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 upstream auth provider.
type UpstreamBasicAuthProvider struct {
// HeaderName is the header name to be used to fill upstream auth with.
HeaderName string
// AuthValue is the value of auth header.
AuthValue string
}

// Fill sets the request's HeaderName with AuthValue
func (u UpstreamBasicAuthProvider) Fill(r *http.Request) {
r.Header.Add(u.HeaderName, u.AuthValue)
}
Loading

0 comments on commit 475898c

Please sign in to comment.