Skip to content

Commit

Permalink
Implement admin login
Browse files Browse the repository at this point in the history
  • Loading branch information
J12934 committed Nov 2, 2024
1 parent 2c460d5 commit ec2924d
Show file tree
Hide file tree
Showing 4 changed files with 119 additions and 6 deletions.
11 changes: 11 additions & 0 deletions balancer/pkg/bundle/bundle.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@ type Config struct {
JuiceShopConfig JuiceShopConfig `json:"juiceShop"`
MaxInstances int `json:"maxInstances"`
CookieConfig CookieConfig `json:"cookie"`
AdminConfig *AdminConfig
}

type AdminConfig struct {
Password string `json:"password"`
}

type CookieConfig struct {
Expand Down Expand Up @@ -116,12 +121,18 @@ func New() *Bundle {
panic(errors.New("environment variable 'MULTI_JUICER_CONFIG_COOKIE_SIGNING_KEY' must be set"))
}

adminPasswordKey := os.Getenv("MULTI_JUICER_CONFIG_ADMIN_PASSWORD")
if adminPasswordKey == "" {
panic(errors.New("environment variable 'MULTI_JUICER_CONFIG_ADMIN_PASSWORD' must be set"))
}

config, err := readConfigFromFile("/config/config.json")
if err != nil {
panic(err)
}

config.CookieConfig.SigningKey = cookieSigningKey
config.AdminConfig = &AdminConfig{Password: adminPasswordKey}

// read /challenges.json file
challengesBytes, err := os.ReadFile("/challenges.json")
Expand Down
3 changes: 3 additions & 0 deletions balancer/pkg/testutil/testUtils.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@ func NewTestBundleWithCustomFakeClient(clientset kubernetes.Interface) *bundle.B
Name: "team",
Secure: false,
},
AdminConfig: &bundle.AdminConfig{
Password: "mock-admin-password",
},
},
}
}
Expand Down
41 changes: 41 additions & 0 deletions balancer/routes/join.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,12 @@ func init() {
func handleTeamJoin(bundle *bundle.Bundle) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
team := r.PathValue("team")

if team == "admin" {
handleAdminLogin(bundle, w, r)
return
}

deployment, err := getDeployment(r.Context(), bundle, team)
if err != nil && errors.IsNotFound(err) {
isMaxLimitReached, err := isMaxInstanceLimitReached(r.Context(), bundle)
Expand All @@ -65,6 +71,41 @@ func handleTeamJoin(bundle *bundle.Bundle) http.Handler {
})
}

func handleAdminLogin(bundle *bundle.Bundle, w http.ResponseWriter, r *http.Request) {
if r.Body == nil {
writeUnauthorizedResponse(w)
return
}
body, err := io.ReadAll(r.Body)
if err != nil {
http.Error(w, "failed to read request body", http.StatusInternalServerError)
return
}

var requestBody joinRequestBody
if err := json.Unmarshal(body, &requestBody); err != nil {
http.Error(w, "invalid json", http.StatusBadRequest)
return
}

if requestBody.Passcode != bundle.Config.AdminConfig.Password {
failedLoginCounter.WithLabelValues("admin").Inc()
writeUnauthorizedResponse(w)
return
}

err = setSignedTeamCookie(bundle, "admin", w)
if err != nil {
http.Error(w, "failed to sign team cookie", http.StatusInternalServerError)
return
}

w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
w.Write([]byte(`{"message": "Signed in as admin"}`))
loginCounter.WithLabelValues("login", "admin").Inc()
}

func getDeployment(context context.Context, bundle *bundle.Bundle, team string) (*appsv1.Deployment, error) {
return bundle.ClientSet.AppsV1().Deployments(bundle.RuntimeEnvironment.Namespace).Get(
context,
Expand Down
70 changes: 64 additions & 6 deletions balancer/routes/join_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -201,8 +201,6 @@ func TestJoinHandler(t *testing.T) {

server.ServeHTTP(rr, req)

actions := clientset.Actions()
_ = actions
assert.Equal(t, http.StatusUnauthorized, rr.Code)
assert.Equal(t, "", rr.Header().Get("Set-Cookie"))
})
Expand All @@ -221,8 +219,6 @@ func TestJoinHandler(t *testing.T) {

server.ServeHTTP(rr, req)

actions := clientset.Actions()
_ = actions
assert.Equal(t, http.StatusOK, rr.Code)
assert.Regexp(t, regexp.MustCompile(`team=foobar\..*; Path=/; HttpOnly; SameSite=Strict`), rr.Header().Get("Set-Cookie"))
})
Expand All @@ -241,9 +237,71 @@ func TestJoinHandler(t *testing.T) {

server.ServeHTTP(rr, req)

actions := clientset.Actions()
_ = actions
assert.Equal(t, http.StatusUnauthorized, rr.Code)
assert.Equal(t, "", rr.Header().Get("Set-Cookie"))
})

t.Run("allows admins login with the correct passcode", func(t *testing.T) {
jsonPayload, _ := json.Marshal(map[string]string{"passcode": "mock-admin-password"})
req, _ := http.NewRequest("POST", "/balancer/teams/admin/join", bytes.NewReader(jsonPayload))
rr := httptest.NewRecorder()

server := http.NewServeMux()

bundle := testutil.NewTestBundle()
AddRoutes(server, bundle)

server.ServeHTTP(rr, req)

assert.Equal(t, http.StatusOK, rr.Code)
assert.Regexp(t, regexp.MustCompile(`team=admin\..*; Path=/; HttpOnly; SameSite=Strict`), rr.Header().Get("Set-Cookie"))
})

t.Run("admin login returns usual 'requires auth' response when it get's no request body passed", func(t *testing.T) {
req, _ := http.NewRequest("POST", "/balancer/teams/admin/join", nil)
rr := httptest.NewRecorder()

server := http.NewServeMux()

bundle := testutil.NewTestBundle()
AddRoutes(server, bundle)

server.ServeHTTP(rr, req)

assert.Equal(t, http.StatusUnauthorized, rr.Code)
assert.Equal(t, "", rr.Header().Get("Set-Cookie"))
})

t.Run("admin account requires the correct passcod", func(t *testing.T) {
jsonPayload, _ := json.Marshal(map[string]string{"passcode": "wrong-password"})
req, _ := http.NewRequest("POST", "/balancer/teams/admin/join", bytes.NewReader(jsonPayload))
rr := httptest.NewRecorder()

server := http.NewServeMux()

bundle := testutil.NewTestBundle()
AddRoutes(server, bundle)

server.ServeHTTP(rr, req)

assert.Equal(t, http.StatusUnauthorized, rr.Code)
assert.Equal(t, "", rr.Header().Get("Set-Cookie"))
})

t.Run("admin login doesn't make any kubernetes api calls / creates not kubernetes resources", func(t *testing.T) {
jsonPayload, _ := json.Marshal(map[string]string{"passcode": "mock-admin-password"})
req, _ := http.NewRequest("POST", "/balancer/teams/admin/join", bytes.NewReader(jsonPayload))
rr := httptest.NewRecorder()

server := http.NewServeMux()

clientset := fake.NewSimpleClientset(balancerDeployment)
bundle := testutil.NewTestBundleWithCustomFakeClient(clientset)
AddRoutes(server, bundle)

server.ServeHTTP(rr, req)

assert.Equal(t, http.StatusOK, rr.Code)
assert.Len(t, clientset.Actions(), 0)
})
}

0 comments on commit ec2924d

Please sign in to comment.