Skip to content

Commit

Permalink
Reimplement listing instance endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
J12934 committed Nov 2, 2024
1 parent ec2924d commit 0bc918f
Show file tree
Hide file tree
Showing 3 changed files with 175 additions and 0 deletions.
75 changes: 75 additions & 0 deletions balancer/routes/adminListInstances.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package routes

import (
"encoding/json"
"net/http"
"strconv"
"time"

"github.com/juice-shop/multi-juicer/balancer/pkg/bundle"
"github.com/juice-shop/multi-juicer/balancer/pkg/teamcookie"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

type AdminListInstancesResponse struct {
Instances []AdminListJuiceShopInstance `json:"instances"`
}

type AdminListJuiceShopInstance struct {
Team string `json:"team"`
Ready bool `json:"ready"`
CreatedAt int64 `json:"createdAt"`
LastConnect int64 `json:"lastConnect"`
}

func handleAdminListInstances(bundle *bundle.Bundle) http.Handler {
return http.HandlerFunc(
func(responseWriter http.ResponseWriter, req *http.Request) {
team, err := teamcookie.GetTeamFromRequest(bundle, req)
if err != nil || team != "admin" {
http.Error(responseWriter, "", http.StatusUnauthorized)
return
}

deployments, err := bundle.ClientSet.AppsV1().Deployments(bundle.RuntimeEnvironment.Namespace).List(req.Context(), metav1.ListOptions{
LabelSelector: "app.kubernetes.io/name=juice-shop,app.kubernetes.io/part-of=multi-juicer",
})
if err != nil {
bundle.Log.Printf("Failed to list deployments: %s", err)
http.Error(responseWriter, "unable to get instances", http.StatusInternalServerError)
return
}

instances := []AdminListJuiceShopInstance{}
for _, teamDeployment := range deployments.Items {

lastConnectAnnotation := teamDeployment.Annotations["multi-juicer.owasp-juice.shop/lastRequest"]
lastConnection := time.UnixMilli(0)

if lastConnectAnnotation != "" {
millis, err := strconv.ParseInt(lastConnectAnnotation, 10, 64)
if err != nil {
millis = 0
}
lastConnection = time.UnixMilli(millis)
}

instances = append(instances, AdminListJuiceShopInstance{
Team: teamDeployment.Labels["team"],
Ready: teamDeployment.Status.ReadyReplicas == 1,
CreatedAt: teamDeployment.CreationTimestamp.UnixMilli(),
LastConnect: lastConnection.UnixMilli(),
})
}

response := AdminListInstancesResponse{
Instances: instances,
}

responseBody, _ := json.Marshal(response)
responseWriter.Header().Set("Content-Type", "application/json")
responseWriter.WriteHeader(http.StatusOK)
responseWriter.Write(responseBody)
},
)
}
99 changes: 99 additions & 0 deletions balancer/routes/adminListInstances_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package routes

import (
"encoding/json"
"fmt"
"net/http"
"net/http/httptest"
"testing"
"time"

"github.com/juice-shop/multi-juicer/balancer/pkg/testutil"
"github.com/stretchr/testify/assert"
appsv1 "k8s.io/api/apps/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes/fake"
)

func TestAdminListInstanceshandler(t *testing.T) {
createTeam := func(team string, createdAt time.Time, lastRequest time.Time, readyReplicas int32) *appsv1.Deployment {
return &appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{
Name: fmt.Sprintf("juiceshop-%s", team),
Namespace: "test-namespace",
CreationTimestamp: metav1.Time{
Time: createdAt,
},
Annotations: map[string]string{
"multi-juicer.owasp-juice.shop/challenges": "[]",
"multi-juicer.owasp-juice.shop/challengesSolved": "0",
"multi-juicer.owasp-juice.shop/lastRequest": fmt.Sprintf("%d", lastRequest.UnixMilli()),
"multi-juicer.owasp-juice.shop/lastRequestReadable": "2024-10-18 13:55:18.08198884+0000 UTC m=+11.556786174",
"multi-juicer.owasp-juice.shop/passcode": "$2a$10$wnxvqClPk/13SbdowdJtu.2thGxrZe4qrsaVdTVUsYIrVVClhPMfS",
},
Labels: map[string]string{
"app.kubernetes.io/name": "juice-shop",
"app.kubernetes.io/part-of": "multi-juicer",
"team": team,
},
},
Status: appsv1.DeploymentStatus{
ReadyReplicas: readyReplicas,
},
}
}

t.Run("listing instances requires admin login", func(t *testing.T) {
req, _ := http.NewRequest("GET", "/balancer/admin/all", nil)
req.Header.Set("Cookie", fmt.Sprintf("team=%s", testutil.SignTestTeamname("some team")))
rr := httptest.NewRecorder()

server := http.NewServeMux()

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

server.ServeHTTP(rr, req)

assert.Equal(t, http.StatusUnauthorized, rr.Code)
assert.Equal(t, "\n", rr.Body.String())
})

t.Run("lists all juice shop instances", func(t *testing.T) {
req, _ := http.NewRequest("GET", "/balancer/admin/all", nil)
req.Header.Set("Cookie", fmt.Sprintf("team=%s", testutil.SignTestTeamname("admin")))
rr := httptest.NewRecorder()

server := http.NewServeMux()

clientset := fake.NewSimpleClientset(
createTeam("foobar", time.UnixMilli(1_700_000_000_000), time.UnixMilli(1_729_259_666_123), 1),
createTeam("test-team", time.UnixMilli(1_600_000_000_000), time.UnixMilli(1_729_259_333_123), 0),
)
bundle := testutil.NewTestBundleWithCustomFakeClient(clientset)
AddRoutes(server, bundle)

server.ServeHTTP(rr, req)
assert.Equal(t, http.StatusOK, rr.Code)

var response AdminListInstancesResponse
err := json.Unmarshal(rr.Body.Bytes(), &response)
assert.Nil(t, err)

assert.Equal(t, []AdminListJuiceShopInstance{
{
Team: "foobar",
Ready: true,
CreatedAt: 1_700_000_000_000,
LastConnect: 1_729_259_666_123,
},
{
Team: "test-team",
Ready: false,
CreatedAt: 1_600_000_000_000,
LastConnect: 1_729_259_333_123,
},
}, response.Instances)
})
}
1 change: 1 addition & 0 deletions balancer/routes/routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ func AddRoutes(
router.Handle("POST /balancer/teams/logout", handleLogout(bundle))
router.Handle("POST /balancer/teams/reset-passcode", handleResetPasscode(bundle))
router.Handle("GET /balancer/score-board/top", handleScoreBoard(bundle))
router.Handle("GET /balancer/admin/all", handleAdminListInstances(bundle))

router.HandleFunc("GET /balancer/health", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
Expand Down

0 comments on commit 0bc918f

Please sign in to comment.