forked from open-horizon/vault-exchange-auth
-
Notifications
You must be signed in to change notification settings - Fork 1
/
auth_agbot.go
142 lines (117 loc) · 5.42 KB
/
auth_agbot.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
package openhorizon
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"strconv"
"strings"
"time"
"github.com/openbao/openbao/sdk/v2/logical"
)
const AGBOTID_RENEW_SECRET = "agbotid"
const AGBOTPW_RENEW_SECRET = "password"
// Attempt to authenticate the caller as an open horizon agbot.
func (o *backend) AuthenticateAsAgbot(exURL string, tok string, renewal int, userOrg, userId, password string) (*logical.Response, error) {
agbots, err := o.verifyAgbotCredentials(exURL, userOrg, userId, password)
if _, ok := err.(NotAuthenticatedError); ok {
o.Logger().Info(ohlog(fmt.Sprintf("(%s/%s) is not authenticated as an agbot: %v", userOrg, userId, err)))
return nil, err
} else if _, ok := err.(OtherError); ok {
o.Logger().Error(ohlog(fmt.Sprintf("error trying to authenticate (%s/%s) as an agbot, error: %v", userOrg, userId, err)))
return nil, err
}
// No errors occured, keep processing the response to ensure it is correct for an authenticated agbot.
agbotId := fmt.Sprintf("%s/%s", userOrg, userId)
foundAgbot := false
// Iterate through the agbots in the response. There should be one or none.
for key, _ := range agbots.Agbots {
// Skip agbots that are not the agbot logging in. This should never occur, just being defensive.
if key != agbotId {
continue
}
// Ensure that the returned key is in the expected {orgid}/{agbotid} format.
if orgAndAgbotId := strings.Split(key, "/"); len(orgAndAgbotId) != 2 {
o.Logger().Error(ohlog(fmt.Sprintf("returned agbot (%s) has unsupported format, should be org/agbotid", key)))
return nil, logical.ErrPermissionDenied
}
// The caller is an Agbot.
foundAgbot = true
// Ensure that the bao ACL policies needed by the agbot are defined in the bao.
err = o.setupAgbotPolicies(tok)
if err != nil {
o.Logger().Error(ohlog(fmt.Sprintf("unable to setup ACL policies for agbot (%s), error: %v", agbotId, err)))
return nil, logical.ErrPermissionDenied
}
}
// The agbot was not found in the exchange, log the error and terminate the login.
if !foundAgbot {
o.Logger().Error(ohlog(fmt.Sprintf("Agbot (%s) was not found in the exchange", agbotId)))
return nil, logical.ErrPermissionDenied
}
// Log a successful authentication.
if o.Logger().IsInfo() {
o.Logger().Info(ohlog(fmt.Sprintf("Agbot (%s) authenticated", agbotId)))
}
// Return the authentication results to the framework.
// This response indicates a couple of things:
// - The access control policies that should be given to an agbot user.
// - Some secret context that the the renew function can check to ensure that it is called by the right entity.
// - The time period for which the token should remain valid, it's a "periodic" token.
// - The token type, which is a long running service token.
// - How the current lease for the token shuld behave, in this case it is renewable. the agbot will actively renew it.
return &logical.Response{
Auth: &logical.Auth{
Policies: []string{AGBOT_POLICY_NAME},
InternalData: map[string]interface{}{
AGBOTID_RENEW_SECRET: agbotId,
AGBOTPW_RENEW_SECRET: password,
},
Metadata: map[string]string{
"agbot": strconv.FormatBool(true),
},
Period: time.Duration(renewal) * time.Second,
TokenType: logical.TokenTypeService,
LeaseOptions: logical.LeaseOptions{
Renewable: true,
},
},
}, nil
}
// Call the openhorizon exchange to validate the caller's credentials as an Agbot. This API call will use the caller's own credentials to verify that it can
// retrieve the definition of it's own idenity from the exchange. This verifies that the caller's creds are good.
func (o *backend) verifyAgbotCredentials(exURL string, userOrg string, userId string, password string) (*GetAgbotsResponse, error) {
// Log the exchange API that we are going to call.
url := fmt.Sprintf("%v/orgs/%v/agbots/%v", exURL, userOrg, userId)
user := fmt.Sprintf("%s/%s", userOrg, userId)
apiMsg := fmt.Sprintf("%v %v", http.MethodGet, url)
if o.Logger().IsDebug() {
o.Logger().Debug(ohlog(fmt.Sprintf("checking exchange API %v", apiMsg)))
}
// Invoke the exchange API to verify the user.
resp, err := o.InvokeExchangeWithRetry(url, user, password)
// If there was an error invoking the HTTP API, return it.
if err != nil {
return nil, OtherError{Msg: err.Error()}
}
// Make sure the response reader is closed if we exit quickly.
defer resp.Body.Close()
// If the response code was not expected, then return the error.
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
if resp.StatusCode == 401 {
return nil, NotAuthenticatedError{Msg: fmt.Sprintf("unable to verify agbot (%s) in the exchange, HTTP code %v, either the agbot is undefined or the agbot's password is incorrect.", userOrg, user, resp.StatusCode)}
} else if resp.StatusCode == 404 {
return nil, NotAuthenticatedError{Msg: fmt.Sprintf("agbot (%s) not found in the exchange, HTTP code %v", user, resp.StatusCode)}
} else {
return nil, OtherError{Msg: fmt.Sprintf("unable to verify agbot (%s) in the exchange, HTTP code %v", user, resp.StatusCode)}
}
}
// Demarshal the response.
agbots := new(GetAgbotsResponse)
if bodyBytes, err := ioutil.ReadAll(resp.Body); err != nil {
return nil, OtherError{Msg: fmt.Sprintf("unable to read HTTP response from %v, error: %v", apiMsg, err)}
} else if err = json.Unmarshal(bodyBytes, agbots); err != nil {
return nil, OtherError{Msg: fmt.Sprintf("failed to unmarshal HTTP response from %s, error: %v", apiMsg, err)}
}
return agbots, nil
}