-
Notifications
You must be signed in to change notification settings - Fork 66
/
authorizer_external.go
144 lines (132 loc) · 4.37 KB
/
authorizer_external.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
package main
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"k8s.io/apiserver/pkg/authentication/user"
"net/http"
"strconv"
"strings"
"time"
"github.com/arrikto/oidc-authservice/common"
)
// ExternalAuthorizer is responsible for handling authorization in an external
// authorization server.
type ExternalAuthorizer struct {
url string
}
// AuthorizationRequestBody is the object with the current request metadata that
// the ExternalAuthorizer will send to external authorizer.
type AuthorizationRequestBody struct {
Timestamp string `json:"timestamp"`
User AuthorizationUserInfo `json:"user"`
Request AuthorizationRequestInfo `json:"request"`
}
// AuthorizationUserInfo is the sub-object with the user metadata that the
// ExternalAuthorizer will send to the external authorizer.
type AuthorizationUserInfo struct {
Name string `json:"name"`
Id string `json:"id"`
Groups []string `json:"groups"`
Extra map[string][]string `json:"extra"`
Claims map[string]interface{} `json:"claims"`
}
// AuthorizationRequestInfo is the sub-object with the request metadata that the
// ExternalAuthorizer will send to the external authorizer.
type AuthorizationRequestInfo struct {
Host string `json:"host"`
Port int `json:"port"`
Path string `json:"path"`
Method string `json:"method"`
}
func (e ExternalAuthorizer) Authorize(r *http.Request, userinfo user.Info) (allowed bool, reason string, err error) {
// Collect data and create the AuthorizationRequestBody.
logger := common.RequestLogger(r, "external authorizer")
logger = logger.WithField("user", userinfo)
authorizationUserInfo := e.getUserInfo(r, userinfo)
request := e.getRequestInfo(r)
timestamp := time.Now().Format(time.RFC3339)
body := AuthorizationRequestBody{
Timestamp: timestamp,
User: authorizationUserInfo,
Request: request,
}
// Send the request to the external authorizer.
code, responseBody, err := e.doRequest(body)
if err != nil {
return false, "Error while authorizing the request", err
}
// If the response of the external authorizer is in the [200, 300) range
// allow the request.
if code >= 200 && code < 300 {
logger.Infof("Request is allowed")
return true, "", nil
} else if code == 401 || code == 403 {
logger.Infof("Request is not allowed")
return false, fmt.Sprintf("%v", responseBody), nil
}
err = errors.New(fmt.Sprintf("Authorization server returned unexpected status code: %d with body: %v",
code, responseBody))
return false, "", err
}
// getUserInfo creates a AuthorizationUserInfo object for the current context.
func (e ExternalAuthorizer) getUserInfo(r *http.Request, userinfo user.Info) AuthorizationUserInfo {
// Parse the JWT token and add get the claims if it exists.
bearer := common.GetBearerToken(r.Header.Get("Authorization"))
var parsedJwt map[string]interface{} = nil
if bearer != "" {
jwt, err := common.ParseJWT(bearer)
if err == nil {
// Unmarshal the JSON to the interface.
err = json.Unmarshal(jwt, &parsedJwt)
// Ignore any errors
}
}
return AuthorizationUserInfo{
Name: userinfo.GetName(),
Id: userinfo.GetUID(),
Groups: userinfo.GetGroups(),
Extra: userinfo.GetExtra(),
Claims: parsedJwt,
}
}
// getRequestInfo creates a AuthorizationRequestInfo object for the current
// context.
func (e ExternalAuthorizer) getRequestInfo(r *http.Request) (request AuthorizationRequestInfo) {
host := strings.Split(r.Host, ":")
// Use 80 as a fallback.
var port = 80
if len(host) > 1 {
port, _ = strconv.Atoi(host[1])
}
hostname := host[0]
return AuthorizationRequestInfo{
Host: hostname,
Port: port,
Path: r.URL.Path,
Method: r.Method,
}
}
// doRequest does the request to the external authorization server.
func (e ExternalAuthorizer) doRequest(requestBody AuthorizationRequestBody) (code int, responseBody string, err error) {
// Serialize the object.
b, err := json.Marshal(&requestBody)
if err != nil {
return 0, "", err
}
// Send the request
resp, err := http.Post(e.url, "application/json", bytes.NewBuffer(b))
if err != nil {
err = fmt.Errorf("error sending the request: %w", err)
return 0, "", err
}
defer resp.Body.Close()
response, err := ioutil.ReadAll(resp.Body)
if err != nil {
err = fmt.Errorf("error while reading the body: %w", err)
return 0, "", err
}
return resp.StatusCode, string(response), nil
}