Skip to content

Commit 70a0c81

Browse files
authored
chore: add internal Requester (#79)
1 parent 4921d95 commit 70a0c81

File tree

5 files changed

+192
-143
lines changed

5 files changed

+192
-143
lines changed

auth/internal/requestor.go

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
package internal
2+
3+
import (
4+
"bytes"
5+
"context"
6+
"crypto/tls"
7+
"encoding/json"
8+
"fmt"
9+
"io/ioutil"
10+
"net/http"
11+
"strings"
12+
13+
"github.com/shaj13/go-guardian/v2/auth"
14+
)
15+
16+
// Requester sends an HTTP request to query
17+
// an authorization server to determine the active state of an
18+
// access token and to determine meta-information about this token.
19+
type Requester struct {
20+
Addr string
21+
Endpoint string
22+
Client *http.Client
23+
// AdditionalData add more data to http request
24+
AdditionalData func(r *http.Request)
25+
}
26+
27+
// Do sends the HTTP request and parse the HTTP response.
28+
func (r *Requester) Do(ctx context.Context, data, review, status interface{}) error {
29+
body, err := json.Marshal(data)
30+
if err != nil {
31+
return fmt.Errorf("Failed to marshal request body data, Type: %T, Err: %w", data, err)
32+
}
33+
34+
url := r.Addr + r.Endpoint
35+
36+
req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewBuffer(body))
37+
if err != nil {
38+
return fmt.Errorf("Failed to create new HTTP request, Method: POST, URL: %s, Err: %w", url, err)
39+
}
40+
41+
if r.AdditionalData != nil {
42+
r.AdditionalData(req)
43+
}
44+
45+
req.Header.Set("Content-Type", "application/json")
46+
req.Header.Set("Accept", "application/json")
47+
48+
resp, err := r.Client.Do(req)
49+
if err != nil {
50+
return fmt.Errorf("Failed to send the HTTP request, Method: POST, URL: %s, Err: %w", url, err)
51+
}
52+
53+
body, err = ioutil.ReadAll(resp.Body)
54+
if err != nil {
55+
return fmt.Errorf("Failed to read the HTTP response, Method: POST, URL: %s, Err: %w", url, err)
56+
}
57+
58+
defer resp.Body.Close()
59+
60+
if err := json.Unmarshal(body, status); err == nil {
61+
return nil
62+
}
63+
64+
if err := json.Unmarshal(body, review); err != nil {
65+
return fmt.Errorf("Failed to unmarshal response body data, Type: %T Err: %w", review, err)
66+
}
67+
68+
return nil
69+
}
70+
71+
// SetRequesterBearerToken sets ruqester token.
72+
func SetRequesterBearerToken(token string) auth.Option {
73+
return auth.OptionFunc(func(v interface{}) {
74+
if r, ok := v.(*Requester); ok {
75+
r.AdditionalData = func(r *http.Request) {
76+
r.Header.Set("Authorization", "Bearer "+token)
77+
}
78+
}
79+
})
80+
}
81+
82+
// SetRequesterHTTPClient sets underlying requester http client.
83+
func SetRequesterHTTPClient(c *http.Client) auth.Option {
84+
return auth.OptionFunc(func(v interface{}) {
85+
if r, ok := v.(*Requester); ok {
86+
r.Client = c
87+
}
88+
})
89+
}
90+
91+
// SetRequesterTLSConfig sets underlying requester http client tls config.
92+
func SetRequesterTLSConfig(tls *tls.Config) auth.Option {
93+
return auth.OptionFunc(func(v interface{}) {
94+
if r, ok := v.(*Requester); ok {
95+
r.Client.Transport.(*http.Transport).TLSClientConfig = tls
96+
}
97+
})
98+
}
99+
100+
// SetRequesterClientTransport sets underlying requester http client transport.
101+
func SetRequesterClientTransport(rt http.RoundTripper) auth.Option {
102+
return auth.OptionFunc(func(v interface{}) {
103+
if r, ok := v.(*Requester); ok {
104+
r.Client.Transport = rt
105+
}
106+
})
107+
}
108+
109+
// SetRequesterAddress sets requester origin server address
110+
// e.g http://host:port or https://host:port.
111+
func SetRequesterAddress(addr string) auth.Option {
112+
return auth.OptionFunc(func(v interface{}) {
113+
if r, ok := v.(*Requester); ok {
114+
r.Addr = strings.TrimSuffix(addr, "/")
115+
}
116+
})
117+
}
118+
119+
// SetRequesterEndpoint sets requester origin server endpoint.
120+
// e.g /api/v1/token
121+
func SetRequesterEndpoint(endpoint string) auth.Option {
122+
return auth.OptionFunc(func(v interface{}) {
123+
if r, ok := v.(*Requester); ok {
124+
r.Addr = "/" + strings.TrimSuffix(endpoint, "/")
125+
}
126+
})
127+
}

auth/strategies/kubernetes/kubernetes.go

Lines changed: 30 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -5,103 +5,54 @@
55
package kubernetes
66

77
import (
8-
"bytes"
98
"context"
10-
"encoding/json"
119
"fmt"
12-
"io/ioutil"
1310
"net/http"
14-
"strings"
1511
"time"
1612

1713
kubeauth "k8s.io/api/authentication/v1"
1814
kubemeta "k8s.io/apimachinery/pkg/apis/meta/v1"
1915

2016
"github.com/shaj13/go-guardian/v2/auth"
17+
"github.com/shaj13/go-guardian/v2/auth/internal"
2118
"github.com/shaj13/go-guardian/v2/auth/strategies/token"
2219
)
2320

2421
type kubeReview struct {
25-
addr string
26-
// service account token
27-
token string
28-
apiVersion string
29-
audiences []string
30-
client *http.Client
22+
requester *internal.Requester
23+
audiences []string
3124
}
3225

3326
func (k *kubeReview) authenticate(ctx context.Context, r *http.Request, token string) (auth.Info, time.Time, error) {
34-
var t time.Time
35-
36-
tr := &kubeauth.TokenReview{
27+
t := time.Time{}
28+
status := &kubemeta.Status{}
29+
review := &kubeauth.TokenReview{}
30+
data := &kubeauth.TokenReview{
3731
Spec: kubeauth.TokenReviewSpec{
3832
Token: token,
3933
Audiences: k.audiences,
4034
},
4135
}
4236

43-
body, err := json.Marshal(tr)
44-
if err != nil {
45-
return nil, t, fmt.Errorf(
46-
"strategies/kubernetes: Failed to Marshal TokenReview Err: %s",
47-
err,
48-
)
49-
}
50-
51-
url := k.addr + "/apis/" + k.apiVersion + "/tokenreviews"
52-
53-
req, err := http.NewRequest(http.MethodPost, url, bytes.NewBuffer(body))
54-
if err != nil {
55-
return nil, t, err
56-
}
57-
58-
req.Header.Set("Authorization", "Bearer "+k.token)
59-
req.Header.Set("Content-Type", "application/json")
60-
req.Header.Set("Accept", "application/json")
61-
62-
resp, err := k.client.Do(req)
63-
if err != nil {
64-
return nil, t, err
65-
}
66-
67-
body, err = ioutil.ReadAll(resp.Body)
68-
if err != nil {
69-
return nil, t, err
70-
}
71-
72-
defer resp.Body.Close()
37+
err := k.requester.Do(ctx, data, review, status)
7338

74-
// verify the response is not an kubernetes status error.
75-
status := &kubemeta.Status{}
76-
err = json.Unmarshal(body, status)
77-
if err == nil && status.Status != kubemeta.StatusSuccess {
39+
switch {
40+
case err != nil:
41+
return nil, t, fmt.Errorf("strategies/kubernetes: %w", err)
42+
case len(status.Status) > 0 && status.Status != kubemeta.StatusSuccess:
7843
return nil, t, fmt.Errorf("strategies/kubernetes: %s", status.Message)
79-
}
80-
81-
tr = &kubeauth.TokenReview{}
82-
err = json.Unmarshal(body, tr)
83-
if err != nil {
84-
return nil, t, fmt.Errorf(
85-
"strategies/kubernetes: Failed to Unmarshal Response body to TokenReview Err: %s",
86-
err,
87-
)
88-
}
89-
90-
if len(tr.Status.Error) > 0 {
91-
return nil, t, fmt.Errorf("strategies/kubernetes: %s", tr.Status.Error)
92-
}
93-
94-
if !tr.Status.Authenticated {
44+
case len(review.Status.Error) > 0:
45+
return nil, t, fmt.Errorf("strategies/kubernetes: Failed to authenticate token")
46+
case !review.Status.Authenticated:
9547
return nil, t, fmt.Errorf("strategies/kubernetes: Token Unauthorized")
48+
default:
49+
user := review.Status.User
50+
extensions := make(map[string][]string)
51+
for k, v := range user.Extra {
52+
extensions[k] = v
53+
}
54+
return auth.NewUserInfo(user.Username, user.UID, user.Groups, extensions), t, nil
9655
}
97-
98-
user := tr.Status.User
99-
extensions := make(map[string][]string)
100-
for k, v := range user.Extra {
101-
extensions[k] = v
102-
}
103-
104-
return auth.NewUserInfo(user.Username, user.UID, user.Groups, extensions), t, nil
10556
}
10657

10758
// GetAuthenticateFunc return function to authenticate request using kubernetes token review.
@@ -118,19 +69,21 @@ func New(c auth.Cache, opts ...auth.Option) auth.Strategy {
11869
}
11970

12071
func newKubeReview(opts ...auth.Option) *kubeReview {
72+
12173
kr := &kubeReview{
122-
addr: "http://127.0.0.1:6443",
123-
apiVersion: "authentication.k8s.io/v1",
124-
client: &http.Client{
125-
Transport: &http.Transport{},
74+
requester: &internal.Requester{
75+
Addr: "http://127.0.0.1:6443",
76+
Endpoint: "/apis/authentication.k8s.io/v1/tokenreviews",
77+
Client: &http.Client{
78+
Transport: &http.Transport{},
79+
},
12680
},
12781
}
12882

12983
for _, opt := range opts {
13084
opt.Apply(kr)
85+
opt.Apply(kr.requester)
13186
}
13287

133-
kr.addr = strings.TrimSuffix(kr.addr, "/")
134-
kr.apiVersion = strings.TrimPrefix(strings.TrimSuffix(kr.apiVersion, "/"), "/")
13588
return kr
13689
}

auth/strategies/kubernetes/kubernetes_test.go

Lines changed: 11 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,10 @@ import (
1616
func TestNewKubeReview(t *testing.T) {
1717
// Round #1 -- check default
1818
kr := newKubeReview()
19-
assert.NotNil(t, kr.client)
20-
assert.NotNil(t, kr.client.Transport)
21-
assert.Equal(t, kr.apiVersion, "authentication.k8s.io/v1")
22-
assert.Equal(t, kr.addr, "http://127.0.0.1:6443")
23-
24-
// Round #2 -- apply opt and trim "/"
25-
ver := SetAPIVersion("/test/v1/")
26-
addr := SetAddress("http://127.0.0.1:8080/")
27-
kr = newKubeReview(ver, addr)
28-
assert.Equal(t, kr.apiVersion, "test/v1")
29-
assert.Equal(t, kr.addr, "http://127.0.0.1:8080")
19+
assert.NotNil(t, kr.requester.Client)
20+
assert.NotNil(t, kr.requester.Client.Transport)
21+
assert.Equal(t, kr.requester.Endpoint, "/apis/authentication.k8s.io/v1/tokenreviews")
22+
assert.Equal(t, kr.requester.Addr, "http://127.0.0.1:6443")
3023
}
3124

3225
func TestKubeReview(t *testing.T) {
@@ -47,7 +40,7 @@ func TestKubeReview(t *testing.T) {
4740
name: "it return error when server return invalid token review",
4841
code: 200,
4942
file: "invalid_token_review",
50-
err: fmt.Errorf(`strategies/kubernetes: Failed to Unmarshal Response body to TokenReview Err: invalid character 'i' looking for beginning of value`),
43+
err: fmt.Errorf(`strategies/kubernetes: Failed to unmarshal response body data, Type: *v1.TokenReview Err: invalid character 'i' looking for beginning of value`),
5144
},
5245
{
5346
name: "it return error when server return Status.Error",
@@ -72,14 +65,15 @@ func TestKubeReview(t *testing.T) {
7265
for _, tt := range table {
7366
t.Run(tt.name, func(t *testing.T) {
7467
srv := mockKubeAPIServer(t, tt.file, tt.code)
75-
kr := &kubeReview{
76-
addr: srv.URL,
77-
client: srv.Client(),
78-
}
68+
addr := SetAddress(srv.URL)
69+
client := SetHTTPClient(srv.Client())
70+
kr := newKubeReview(addr, client)
7971
r, _ := http.NewRequest("", "", nil)
8072
info, _, err := kr.authenticate(r.Context(), r, "")
8173

82-
assert.Equal(t, tt.err, err)
74+
if tt.err != nil {
75+
assert.EqualError(t, err, tt.err.Error())
76+
}
8377
assert.Equal(t, tt.info, info)
8478
})
8579
}

0 commit comments

Comments
 (0)