Skip to content

Commit

Permalink
feat(api): implement authsvc in uds package and use jwt in api (#335)
Browse files Browse the repository at this point in the history
  • Loading branch information
TristanHoladay authored Sep 19, 2024
1 parent a36a1ef commit def3b83
Show file tree
Hide file tree
Showing 8 changed files with 110 additions and 6 deletions.
3 changes: 3 additions & 0 deletions chart/templates/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,6 @@ spec:
limits:
memory: "512Mi"
cpu: "750m"
env:
- name: AUTH_SVC_ENABLED
value: {{ .Values.sso.enabled | quote }}
14 changes: 13 additions & 1 deletion chart/templates/uds-package.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ spec:
- service: uds-runtime
selector:
app: uds-runtime
host: {{ .Values.package.host}}
host: {{ .Values.package.host }}
gateway: {{ .Values.package.gateway }}
port: 8080
targetPort: 8080
Expand All @@ -18,3 +18,15 @@ spec:
selector:
app: uds-runtime
remoteGenerated: KubeAPI
{{- if .Values.sso.enabled }}
sso:
- name: uds-runtime
clientId: runtime
redirectUris:
- "https://{{ .Values.package.host}}.{{ .Values.package.gateway }}.{{ .Values.package.domain }}/auth"
enableAuthserviceSelector:
app: uds-runtime
groups:
anyOf:
- /UDS Core/Admin
{{- end }}
3 changes: 3 additions & 0 deletions chart/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ image:
tag: 0.3.0
# x-release-please-end
pullPolicy: IfNotPresent
sso:
enabled: true
package:
gateway: admin
host: runtime
domain: "###ZARF_VAR_DOMAIN###"
4 changes: 4 additions & 0 deletions hack/nightly/nightly-values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,7 @@ image:
package:
gateway: tenant
host: runtime-canary

# no sso for runtime-canary
sso:
enabled: false
17 changes: 15 additions & 2 deletions pkg/api/auth/token.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import (
"net/http"
)

// RequireSecret ensures the request has a valid token.
func RequireSecret(validToken string) func(http.Handler) http.Handler {
// RequireLocalToken ensures the request has a valid token.
func RequireLocalToken(validToken string) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
token := r.URL.Query().Get("token")
Expand All @@ -23,6 +23,19 @@ func RequireSecret(validToken string) func(http.Handler) http.Handler {
}
}

func RequireJWT(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
token := r.Header.Get("Authorization")

if token == "" {
w.WriteHeader(http.StatusUnauthorized)
return
}

next.ServeHTTP(w, r)
})
}

// Connect is a head-only request to test the connection.
func Connect(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusOK)
Expand Down
22 changes: 19 additions & 3 deletions pkg/api/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,13 @@ type K8sResources struct {
// @BasePath /api/v1
// @schemes http https
func Setup(assets *embed.FS) (*chi.Mux, error) {
apiAuth, token, err := setAuth()
apiAuth, token, err := checkForLocalAuth()
if err != nil {
return nil, fmt.Errorf("failed to set auth: %w", err)
}

authSVC := checkForClusterAuth()

r := chi.NewRouter()

r.Use(udsMiddleware.ConditionalCompress)
Expand Down Expand Up @@ -99,10 +101,15 @@ func Setup(assets *embed.FS) (*chi.Mux, error) {
// Require a valid token for API calls
if apiAuth {
// If api auth is enabled, require a valid token for all routes under /api/v1
r.Use(auth.RequireSecret(token))
r.Use(auth.RequireLocalToken(token))
// Endpoint to test if connected with auth
r.Head("/", auth.Connect)
}

if authSVC {
r.Use(auth.RequireJWT)
}

r.Route("/monitor", func(r chi.Router) {
r.Get("/pepr/", monitor.Pepr)
r.Get("/pepr/{stream}", monitor.Pepr)
Expand Down Expand Up @@ -310,7 +317,7 @@ func fileServer(r chi.Router, root http.FileSystem) error {
return nil
}

func setAuth() (bool, string, error) {
func checkForLocalAuth() (bool, string, error) {
apiAuth := true
if strings.ToLower(os.Getenv("API_AUTH_DISABLED")) == "true" {
apiAuth = false
Expand All @@ -330,6 +337,15 @@ func setAuth() (bool, string, error) {
return apiAuth, token, nil
}

func checkForClusterAuth() bool {
authSVC := false
if strings.ToLower(os.Getenv("AUTH_SVC_ENABLED")) == "true" {
authSVC = true
}

return authSVC
}

func serveAuthStatus(w http.ResponseWriter, _ *http.Request) {
authStatus := map[string]string{
"API_AUTH_DISABLED": os.Getenv("API_AUTH_DISABLED"),
Expand Down
49 changes: 49 additions & 0 deletions pkg/test/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,55 @@ func TestClusterHealth(t *testing.T) {
})
}

func TestAuthSVCToken(t *testing.T) {
// must set before setup() is called
os.Setenv("AUTH_SVC_ENABLED", "true")
defer os.Unsetenv("AUTH_SVC_ENABLED")

r, err := setup()
require.NoError(t, err)

defer teardown()

t.Run("authorized with token", func(t *testing.T) {
// Create a new context with a timeout
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
defer cancel()

rr := httptest.NewRecorder()
req := httptest.NewRequest("GET", "/api/v1/resources/workloads/pods", nil)
req.Header.Set("Authorization", "Bearer valid-token")

// Start serving the request for 1 second
go func(ctx context.Context) {
r.ServeHTTP(rr, req)
}(ctx)

// wait for the context to be done
<-ctx.Done()
require.Equal(t, http.StatusOK, rr.Code)
require.NotEmpty(t, rr.Body.String())
})

t.Run("no token", func(t *testing.T) {
// Create a new context with a timeout
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
defer cancel()

rr := httptest.NewRecorder()
req := httptest.NewRequest("GET", "/api/v1/resources/workloads/pods", nil)

// Start serving the request for 1 second
go func(ctx context.Context) {
r.ServeHTTP(rr, req)
}(ctx)

// wait for the context to be done
<-ctx.Done()
require.Equal(t, http.StatusUnauthorized, rr.Code)
})
}

func TestTopLevelResourceRoutes(t *testing.T) {
r, err := setup()
require.NoError(t, err)
Expand Down
4 changes: 4 additions & 0 deletions zarf.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ metadata:
version: 0.3.0
# x-release-please-end

variables:
- name: DOMAIN
default: "uds.dev"

components:
- name: uds-runtime
required: true
Expand Down

0 comments on commit def3b83

Please sign in to comment.