Skip to content

Commit 91399c2

Browse files
authored
Refactor backend code (#509)
* separate spire and tornjak APIs Signed-off-by: Maia Iyer <maia.raj.iyer@gmail.com> * refactored api/agent/server.go Signed-off-by: Maia Iyer <maia.raj.iyer@gmail.com> * Fix CodeQL check Signed-off-by: Maia Iyer <maia.raj.iyer@gmail.com> * nit indentation Signed-off-by: Maia Iyer <maia.raj.iyer@gmail.com> --------- Signed-off-by: Maia Iyer <maia.raj.iyer@gmail.com>
1 parent b922906 commit 91399c2

File tree

5 files changed

+1625
-1602
lines changed

5 files changed

+1625
-1602
lines changed

api/agent/config.go

Lines changed: 267 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,267 @@
1+
package api
2+
3+
import (
4+
"fmt"
5+
"strings"
6+
"time"
7+
8+
backoff "github.com/cenkalti/backoff/v4"
9+
"github.com/hashicorp/hcl"
10+
"github.com/hashicorp/hcl/hcl/ast"
11+
"github.com/hashicorp/hcl/hcl/token"
12+
"github.com/pkg/errors"
13+
14+
"github.com/spiffe/tornjak/pkg/agent/authentication/authenticator"
15+
"github.com/spiffe/tornjak/pkg/agent/authorization"
16+
agentdb "github.com/spiffe/tornjak/pkg/agent/db"
17+
)
18+
19+
func stringFromToken(keyToken token.Token) (string, error) {
20+
switch keyToken.Type {
21+
case token.STRING, token.IDENT:
22+
default:
23+
return "", fmt.Errorf("expected STRING or IDENT but got %s", keyToken.Type)
24+
}
25+
value := keyToken.Value()
26+
stringValue, ok := value.(string)
27+
if !ok {
28+
// purely defensive
29+
return "", fmt.Errorf("expected %T but got %T", stringValue, value)
30+
}
31+
return stringValue, nil
32+
}
33+
34+
// getPluginConfig returns first plugin configuration
35+
func getPluginConfig(plugin *ast.ObjectItem) (string, ast.Node, error) {
36+
// extract plugin name and value
37+
pluginName, err := stringFromToken(plugin.Keys[1].Token)
38+
if err != nil {
39+
return "", nil, fmt.Errorf("invalid plugin type name %q: %w", plugin.Keys[1].Token.Text, err)
40+
}
41+
// extract data
42+
var hclPluginConfig hclPluginConfig
43+
if err := hcl.DecodeObject(&hclPluginConfig, plugin.Val); err != nil {
44+
return "", nil, fmt.Errorf("failed to decode plugin config for %q: %w", pluginName, err)
45+
}
46+
return pluginName, hclPluginConfig.PluginData, nil
47+
}
48+
49+
// NewAgentsDB returns a new agents DB, given a DB connection string
50+
func NewAgentsDB(dbPlugin *ast.ObjectItem) (agentdb.AgentDB, error) {
51+
key, data, err := getPluginConfig(dbPlugin)
52+
if err != nil { // db is required config
53+
return nil, errors.New("Required DataStore plugin not configured")
54+
}
55+
56+
switch key {
57+
case "sql":
58+
// check if data is defined
59+
if data == nil {
60+
return nil, errors.New("SQL DataStore plugin ('config > plugins > DataStore sql > plugin_data') not populated")
61+
}
62+
fmt.Printf("SQL DATASTORE DATA: %+v\n", data)
63+
64+
// TODO can probably add this to config
65+
expBackoff := backoff.NewExponentialBackOff()
66+
expBackoff.MaxElapsedTime = time.Second
67+
68+
// decode config to struct
69+
var config pluginDataStoreSQL
70+
if err := hcl.DecodeObject(&config, data); err != nil {
71+
return nil, errors.Errorf("Couldn't parse DB config: %v", err)
72+
}
73+
74+
// create db
75+
drivername := config.Drivername
76+
dbfile := config.Filename
77+
78+
db, err := agentdb.NewLocalSqliteDB(drivername, dbfile, expBackoff)
79+
if err != nil {
80+
return nil, errors.Errorf("Could not start DB driver %s, filename: %s: %v", drivername, dbfile, err)
81+
}
82+
return db, nil
83+
default:
84+
return nil, errors.Errorf("Couldn't create datastore")
85+
}
86+
}
87+
88+
// NewAuthenticator returns a new Authenticator
89+
func NewAuthenticator(authenticatorPlugin *ast.ObjectItem) (authenticator.Authenticator, error) {
90+
key, data, _ := getPluginConfig(authenticatorPlugin)
91+
92+
switch key {
93+
case "Keycloak":
94+
// check if data is defined
95+
if data == nil {
96+
return nil, errors.New("Keycloak Authenticator plugin ('config > plugins > Authenticator Keycloak > plugin_data') not populated")
97+
}
98+
fmt.Printf("Authenticator Keycloak Plugin Data: %+v\n", data)
99+
// decode config to struct
100+
var config pluginAuthenticatorKeycloak
101+
if err := hcl.DecodeObject(&config, data); err != nil {
102+
return nil, errors.Errorf("Couldn't parse Authenticator config: %v", err)
103+
}
104+
105+
// Log warning if audience is nil that aud claim is not checked
106+
if config.Audience == "" {
107+
fmt.Println("WARNING: Auth plugin has no expected audience configured - `aud` claim will not be checked (please populate 'config > plugins > UserManagement KeycloakAuth > plugin_data > audience')")
108+
}
109+
110+
// create authenticator TODO make json an option?
111+
authenticator, err := authenticator.NewKeycloakAuthenticator(true, config.IssuerURL, config.Audience)
112+
if err != nil {
113+
return nil, errors.Errorf("Couldn't configure Authenticator: %v", err)
114+
}
115+
return authenticator, nil
116+
default:
117+
return nil, errors.Errorf("Invalid option for Authenticator named %s", key)
118+
}
119+
}
120+
121+
// NewAuthorizer returns a new Authorizer
122+
func NewAuthorizer(authorizerPlugin *ast.ObjectItem) (authorization.Authorizer, error) {
123+
key, data, _ := getPluginConfig(authorizerPlugin)
124+
125+
switch key {
126+
case "RBAC":
127+
// check if data is defined
128+
if data == nil {
129+
return nil, errors.New("RBAC Authorizer plugin ('config > plugins > Authorizer RBAC > plugin_data') not populated")
130+
}
131+
fmt.Printf("Authorizer RBAC Plugin Data: %+v\n", data)
132+
133+
// decode config to struct
134+
var config pluginAuthorizerRBAC
135+
if err := hcl.DecodeObject(&config, data); err != nil {
136+
return nil, errors.Errorf("Couldn't parse Authorizer config: %v", err)
137+
}
138+
139+
// decode into role list and apiMapping
140+
roleList := make(map[string]string)
141+
apiMapping := make(map[string][]string)
142+
apiV1Mapping := make(map[string]map[string][]string)
143+
for _, role := range config.RoleList {
144+
roleList[role.Name] = role.Desc
145+
// print warning for empty string
146+
if role.Name == "" {
147+
fmt.Println("WARNING: using the empty string for an API enables access to all authenticated users")
148+
}
149+
}
150+
for _, api := range config.APIRoleMappings {
151+
apiMapping[api.Name] = api.AllowedRoles
152+
fmt.Printf("API name: %s, Allowed Roles: %s \n", api.Name, api.AllowedRoles)
153+
}
154+
for _, apiV1 := range config.APIv1RoleMappings {
155+
arr := strings.Split(apiV1.Name, " ")
156+
apiV1.Method = arr[0]
157+
apiV1.Path = arr[1]
158+
fmt.Printf("API V1 method: %s, API V1 path: %s, API V1 allowed roles: %s \n", apiV1.Method, apiV1.Path, apiV1.AllowedRoles)
159+
if _, ok := apiV1Mapping[apiV1.Path]; ok {
160+
apiV1Mapping[apiV1.Path][apiV1.Method] = apiV1.AllowedRoles
161+
} else {
162+
apiV1Mapping[apiV1.Path] = map[string][]string{apiV1.Method: apiV1.AllowedRoles}
163+
}
164+
}
165+
fmt.Printf("API V1 Mapping: %+v\n", apiV1Mapping)
166+
167+
authorizer, err := authorization.NewRBACAuthorizer(config.Name, roleList, apiMapping, apiV1Mapping)
168+
if err != nil {
169+
return nil, errors.Errorf("Couldn't configure Authorizer: %v", err)
170+
}
171+
return authorizer, nil
172+
default:
173+
return nil, errors.Errorf("Invalid option for Authorizer named %s", key)
174+
}
175+
}
176+
177+
func (s *Server) VerifyConfiguration() error {
178+
if s.TornjakConfig == nil {
179+
return errors.New("config not given")
180+
}
181+
182+
/* Verify server */
183+
if s.TornjakConfig.Server == nil { // must be defined
184+
return errors.New("'config > server' field not defined")
185+
}
186+
if s.TornjakConfig.Server.SPIRESocket == "" {
187+
return errors.New("'config > server > spire_socket_path' field not defined")
188+
}
189+
190+
/* Verify Plugins */
191+
if s.TornjakConfig.Plugins == nil {
192+
return errors.New("'config > plugins' field not defined")
193+
}
194+
return nil
195+
}
196+
197+
func (s *Server) ConfigureDefaults() error {
198+
// no authorization is a default
199+
s.Authenticator = authenticator.NewNullAuthenticator()
200+
s.Authorizer = authorization.NewNullAuthorizer()
201+
return nil
202+
}
203+
204+
func (s *Server) Configure() error {
205+
// Verify Config
206+
err := s.VerifyConfiguration()
207+
if err != nil {
208+
return errors.Errorf("Tornjak Config error: %v", err)
209+
}
210+
211+
/* Configure Server */
212+
serverConfig := s.TornjakConfig.Server
213+
s.SpireServerAddr = serverConfig.SPIRESocket // for convenience
214+
215+
/* Configure Plugins */
216+
// configure defaults for optional plugins, reconfigured if given
217+
// TODO maybe we should not have this step at all
218+
// This is a temporary work around for optional plugin configs
219+
err = s.ConfigureDefaults()
220+
if err != nil {
221+
return errors.Errorf("Tornjak Config error: %v", err)
222+
}
223+
224+
pluginConfigs := *s.TornjakConfig.Plugins
225+
pluginList, ok := pluginConfigs.(*ast.ObjectList)
226+
if !ok {
227+
return fmt.Errorf("expected plugins node type %T but got %T", pluginList, pluginConfigs)
228+
}
229+
230+
// iterate over plugin list
231+
232+
for _, pluginObject := range pluginList.Items {
233+
if len(pluginObject.Keys) != 2 {
234+
return fmt.Errorf("plugin item expected to have two keys (type then name)")
235+
}
236+
237+
pluginType, err := stringFromToken(pluginObject.Keys[0].Token)
238+
if err != nil {
239+
return fmt.Errorf("invalid plugin type key %q: %w", pluginObject.Keys[0].Token.Text, err)
240+
}
241+
242+
// create plugin component based on type
243+
switch pluginType {
244+
// configure datastore
245+
case "DataStore":
246+
s.Db, err = NewAgentsDB(pluginObject)
247+
if err != nil {
248+
return errors.Errorf("Cannot configure datastore plugin: %v", err)
249+
}
250+
// configure Authenticator
251+
case "Authenticator":
252+
s.Authenticator, err = NewAuthenticator(pluginObject)
253+
if err != nil {
254+
return errors.Errorf("Cannot configure Authenticator plugin: %v", err)
255+
}
256+
// configure Authorizer
257+
case "Authorizer":
258+
s.Authorizer, err = NewAuthorizer(pluginObject)
259+
if err != nil {
260+
return errors.Errorf("Cannot configure Authorizer plugin: %v", err)
261+
}
262+
}
263+
// TODO Handle when multiple plugins configured
264+
}
265+
266+
return nil
267+
}

0 commit comments

Comments
 (0)