Skip to content

Commit c10f973

Browse files
committed
Merge branch 'v0.10.0' into ee-v0.10.0
2 parents f02633f + 626cd4d commit c10f973

File tree

13 files changed

+256
-189
lines changed

13 files changed

+256
-189
lines changed

config/config.go

+18-7
Original file line numberDiff line numberDiff line change
@@ -38,17 +38,28 @@ type Project struct {
3838

3939
// Admin stores the admin credentials
4040
type Admin struct {
41-
User string `json:"user" yaml:"user"`
42-
Pass string `json:"pass" yaml:"pass"`
43-
Role string `json:"role" yaml:"role"`
44-
Secret string `json:"secret" yaml:"secret"`
41+
Secret string `json:"secret" yaml:"secret"`
42+
Operation OperationConfig `json:"operatiop"`
43+
Users []AdminUser `json:"users" yaml:"users"`
4544
}
4645

47-
// ProjectAccess defines the access policy for a given project
48-
type ProjectAccess struct {
49-
Name string
46+
// OperationConfig holds the operation mode config
47+
type OperationConfig struct {
48+
Mode int `json:"mode" yaml:"mode"`
49+
Email string `json:"email" yaml:"email"`
50+
Key string `json:"key" yaml:"key"`
5051
}
5152

53+
// AdminUser holds the user credentials and scope
54+
type AdminUser struct {
55+
User string `json:"user" yaml:"user"`
56+
Pass string `json:"pass" yaml:"pass"`
57+
Scopes ProjectScope `json:"scopes" yaml:"scopes"`
58+
}
59+
60+
// ProjectScope contains the project level scope
61+
type ProjectScope map[string][]string // (project name -> []scopes)
62+
5263
// SSL holds the certificate and key file locations
5364
type SSL struct {
5465
Enabled bool `json:"enabled" yaml:"enabled"`

config/generate.go

+9-1
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,19 @@ type input struct {
2727
func GenerateEmptyConfig() *Config {
2828
return &Config{
2929
SSL: &SSL{Enabled: false},
30-
Admin: &Admin{User: "admin", Pass: "123", Role: "captain-cloud", Secret: "some-secret"},
30+
Admin: generateAdmin(),
3131
Projects: []*Project{},
3232
}
3333
}
3434

35+
func generateAdmin() *Admin {
36+
return &Admin{
37+
Secret: "some-secret",
38+
Operation: OperationConfig{Mode: 0},
39+
Users: []AdminUser{{User: "admin", Pass: "123", Scopes: ProjectScope{"all": []string{"all"}}}},
40+
}
41+
}
42+
3543
// GenerateConfig started the interactive cli to generate config file
3644
func GenerateConfig(configFilePath string) error {
3745
fmt.Println()

main.go

+2-12
Original file line numberDiff line numberDiff line change
@@ -76,12 +76,6 @@ var essentialFlags = []cli.Flag{
7676
EnvVar: "ADMIN_PASS",
7777
Value: "",
7878
},
79-
cli.StringFlag{
80-
Name: "admin-role",
81-
Usage: "Set the admin role",
82-
EnvVar: "ADMIN_ROLE",
83-
Value: "",
84-
},
8579
cli.StringFlag{
8680
Name: "admin-sercret",
8781
Usage: "Set the admin secret",
@@ -132,7 +126,6 @@ func actionRun(c *cli.Context) error {
132126
// Flags related to the admin details
133127
adminUser := c.String("admin-user")
134128
adminPass := c.String("admin-pass")
135-
adminRole := c.String("admin-role")
136129
adminSecret := c.String("admin-secret")
137130

138131
// Project and env cannot be changed once space cloud has started
@@ -156,13 +149,10 @@ func actionRun(c *cli.Context) error {
156149

157150
// Override the admin config if provided
158151
if adminUser != "" {
159-
conf.Admin.User = adminUser
152+
conf.Admin.Users[0].User = adminUser
160153
}
161154
if adminPass != "" {
162-
conf.Admin.Pass = adminPass
163-
}
164-
if adminRole != "" {
165-
conf.Admin.Role = adminRole
155+
conf.Admin.Users[0].Pass = adminPass
166156
}
167157
if adminSecret != "" {
168158
conf.Admin.Secret = adminSecret

utils/admin/admin.go

+15-76
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,14 @@ package admin
33
import (
44
"errors"
55
"net/http"
6+
"sync"
67

7-
"github.com/dgrijalva/jwt-go"
88
"github.com/spaceuptech/space-cloud/config"
99
)
1010

1111
// Manager manages all admin transactions
1212
type Manager struct {
13+
lock sync.RWMutex
1314
admin *config.Admin
1415
}
1516

@@ -20,87 +21,25 @@ func New() *Manager {
2021

2122
// SetConfig sets the admin config
2223
func (m *Manager) SetConfig(admin *config.Admin) {
24+
m.lock.Lock()
2325
m.admin = admin
26+
m.lock.Unlock()
2427
}
2528

2629
// Login handles the admin login operation
2730
func (m *Manager) Login(user, pass string) (int, string, error) {
28-
u, p, r := m.admin.User, m.admin.Pass, m.admin.Role
29-
30-
if u != user || p != pass {
31-
return http.StatusUnauthorized, "", errors.New("invalid credentials provided")
32-
}
33-
34-
token, err := m.createToken(map[string]interface{}{"id": r, "role": r})
35-
if err != nil {
36-
return http.StatusInternalServerError, "", err
37-
}
38-
39-
return http.StatusOK, token, nil
40-
}
41-
42-
// IsAdminOpAuthorised checks if the admin operation is authorised
43-
func (m *Manager) IsAdminOpAuthorised(token string) (int, error) {
44-
45-
auth, err := m.parseToken(token)
46-
if err != nil {
47-
return http.StatusUnauthorized, err
48-
}
49-
50-
roleTemp, p := auth["role"]
51-
if !p {
52-
return http.StatusUnauthorized, errors.New("Invalid Token")
53-
}
54-
55-
role := roleTemp.(string)
56-
57-
if role != m.admin.Role {
58-
return http.StatusForbidden, errors.New("You are not authorized to make this request")
59-
}
60-
61-
return http.StatusOK, nil
62-
}
63-
64-
// CreateToken generates a new JWT Token with the token claims
65-
func (m *Manager) createToken(tokenClaims map[string]interface{}) (string, error) {
66-
67-
claims := jwt.MapClaims{}
68-
for k, v := range tokenClaims {
69-
claims[k] = v
70-
}
71-
72-
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
73-
tokenString, err := token.SignedString([]byte(m.admin.Secret))
74-
if err != nil {
75-
return "", err
76-
}
77-
78-
return tokenString, nil
79-
}
80-
81-
func (m *Manager) parseToken(token string) (map[string]interface{}, error) {
82-
// Parse the JWT token
83-
tokenObj, err := jwt.Parse(token, func(token *jwt.Token) (interface{}, error) {
84-
// Don't forget to validate the alg is what you expect:
85-
if token.Method.Alg() != jwt.SigningMethodHS256.Alg() {
86-
return nil, errors.New("invalid signing method type")
31+
m.lock.RLock()
32+
defer m.lock.RUnlock()
33+
34+
for _, u := range m.admin.Users {
35+
if u.User == user && u.Pass == pass {
36+
token, err := m.createToken(map[string]interface{}{"id": user, "role": user})
37+
if err != nil {
38+
return http.StatusInternalServerError, "", err
39+
}
40+
return http.StatusOK, token, nil
8741
}
88-
89-
return []byte(m.admin.Secret), nil
90-
})
91-
if err != nil {
92-
return nil, err
93-
}
94-
95-
// Get the claims
96-
if claims, ok := tokenObj.Claims.(jwt.MapClaims); ok && tokenObj.Valid {
97-
obj := make(map[string]interface{}, len(claims))
98-
for key, val := range claims {
99-
obj[key] = val
100-
}
101-
102-
return obj, nil
10342
}
10443

105-
return nil, errors.New("Admin: JWT token could not be verified")
44+
return http.StatusUnauthorized, "", errors.New("invalid credentials provided")
10645
}

utils/admin/helpers.go

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package admin
2+
3+
import (
4+
"errors"
5+
6+
"github.com/dgrijalva/jwt-go"
7+
)
8+
9+
func (m *Manager) createToken(tokenClaims map[string]interface{}) (string, error) {
10+
11+
claims := jwt.MapClaims{}
12+
for k, v := range tokenClaims {
13+
claims[k] = v
14+
}
15+
16+
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
17+
tokenString, err := token.SignedString([]byte(m.admin.Secret))
18+
if err != nil {
19+
return "", err
20+
}
21+
22+
return tokenString, nil
23+
}
24+
25+
func (m *Manager) parseToken(token string) (map[string]interface{}, error) {
26+
// Parse the JWT token
27+
tokenObj, err := jwt.Parse(token, func(token *jwt.Token) (interface{}, error) {
28+
// Don't forget to validate the alg is what you expect:
29+
if token.Method.Alg() != jwt.SigningMethodHS256.Alg() {
30+
return nil, errors.New("invalid signing method type")
31+
}
32+
33+
return []byte(m.admin.Secret), nil
34+
})
35+
if err != nil {
36+
return nil, err
37+
}
38+
39+
// Get the claims
40+
if claims, ok := tokenObj.Claims.(jwt.MapClaims); ok && tokenObj.Valid {
41+
obj := make(map[string]interface{}, len(claims))
42+
for key, val := range claims {
43+
obj[key] = val
44+
}
45+
46+
return obj, nil
47+
}
48+
49+
return nil, errors.New("Admin: JWT token could not be verified")
50+
}

utils/admin/operations.go

+85
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
package admin
2+
3+
import (
4+
"errors"
5+
"net/http"
6+
7+
"github.com/spaceuptech/space-cloud/config"
8+
"github.com/spaceuptech/space-cloud/utils"
9+
)
10+
11+
// IsTokenValid checks if the token is valid
12+
func (m *Manager) IsTokenValid(token string) error {
13+
m.lock.RLock()
14+
defer m.lock.RUnlock()
15+
16+
_, err := m.parseToken(token)
17+
return err
18+
}
19+
20+
// ValidateSyncOperation validates if an operation is permitted based on the mode
21+
func (m *Manager) ValidateSyncOperation(c *config.Config, project *config.Project) bool {
22+
m.lock.RLock()
23+
defer m.lock.RUnlock()
24+
25+
for _, p := range c.Projects {
26+
if p.ID == project.ID {
27+
return true
28+
}
29+
}
30+
31+
maxProjects := 1
32+
if m.admin.Operation.Mode == 1 {
33+
maxProjects = 3
34+
} else if m.admin.Operation.Mode == 2 {
35+
maxProjects = 5
36+
}
37+
38+
if len(c.Projects) == (maxProjects - 1) {
39+
return true
40+
}
41+
42+
return false
43+
}
44+
45+
// IsAdminOpAuthorised checks if the admin operation is authorised.
46+
// TODO add scope level restrictions as well
47+
func (m *Manager) IsAdminOpAuthorised(token, scope string) (int, error) {
48+
m.lock.RLock()
49+
defer m.lock.RUnlock()
50+
51+
if scope == utils.ScopeDeploy {
52+
if m.admin.Operation.Mode < 1 {
53+
return http.StatusForbidden, errors.New("Operation not supported. Upgrade to avail this feature")
54+
}
55+
}
56+
57+
auth, err := m.parseToken(token)
58+
if err != nil {
59+
return http.StatusUnauthorized, err
60+
}
61+
62+
user, p := auth["id"]
63+
if !p {
64+
return http.StatusUnauthorized, errors.New("Invalid Token")
65+
}
66+
67+
for _, u := range m.admin.Users {
68+
if u.User == user {
69+
70+
// Allow full access for scope name `all`
71+
if _, p := u.Scopes["all"]; p {
72+
return http.StatusOK, nil
73+
}
74+
75+
// Check if scope is present
76+
if _, p := u.Scopes[scope]; p {
77+
return http.StatusOK, nil
78+
}
79+
80+
break
81+
}
82+
}
83+
84+
return http.StatusForbidden, errors.New("You are not authorized to make this request")
85+
}

utils/constants.go

+5
Original file line numberDiff line numberDiff line change
@@ -179,3 +179,8 @@ const (
179179
// Kubernetes is the type used for a kubernetes deployement
180180
Kubernetes OrchestratorType = "kubernetes"
181181
)
182+
183+
const (
184+
// ScopeDeploy is te scope used for the deploy module
185+
ScopeDeploy string = "deploy"
186+
)

0 commit comments

Comments
 (0)