feat: funcs-service v0.1.3 — /funcs/<namespace> URL without token
- /funcs/<namespace>: user opens URL in browser, no auth needed service uses SLESS_SERVICE_TOKEN to query operator internally - /funcs?token=<jwt>: token as query param (bookmarkable URL) - /funcs: returns usage hint with both URL formats - SLESS_SERVICE_TOKEN set via kubectl set env (not stored in repo)
This commit is contained in:
@@ -27,7 +27,7 @@ spec:
|
||||
spec:
|
||||
containers:
|
||||
- name: funcs
|
||||
image: naeel/sless-funcs-service:v0.1.1
|
||||
image: naeel/sless-funcs-service:v0.1.3
|
||||
ports:
|
||||
- containerPort: 8090
|
||||
env:
|
||||
@@ -38,6 +38,8 @@ spec:
|
||||
# Системные функции, скрытые из листинга
|
||||
- name: SLESS_EXCLUDE
|
||||
value: "event-writer,event-monitor,event-cleaner"
|
||||
# Токен сервиса задаётся через kubectl set env или Secret — не хранится в репо
|
||||
# kubectl set env deployment/sless-funcs-service -n sless SLESS_SERVICE_TOKEN="$(cat secrets/test.token)"
|
||||
- name: PORT
|
||||
value: "8090"
|
||||
livenessProbe:
|
||||
|
||||
+43
-18
@@ -56,6 +56,8 @@ func main() {
|
||||
operatorURL := strings.TrimRight(env("SLESS_OPERATOR_URL", "http://sless-operator.sless.svc.cluster.local:9090"), "/")
|
||||
externalURL := strings.TrimRight(env("SLESS_EXTERNAL_URL", ""), "/")
|
||||
port := env("PORT", "8090")
|
||||
// serviceToken — токен сервиса для запросов к оператору по /funcs/<namespace> (без токена пользователя)
|
||||
serviceToken := os.Getenv("SLESS_SERVICE_TOKEN")
|
||||
|
||||
exclude := map[string]bool{}
|
||||
for _, n := range strings.Split(os.Getenv("SLESS_EXCLUDE"), ",") {
|
||||
@@ -64,38 +66,61 @@ func main() {
|
||||
}
|
||||
}
|
||||
|
||||
http.HandleFunc("/funcs", handler(operatorURL, externalURL, exclude))
|
||||
http.HandleFunc("/funcs", handler(operatorURL, externalURL, serviceToken, exclude))
|
||||
// /health — для liveness/readiness probe без Bearer токена
|
||||
http.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
||||
fmt.Fprintln(w, "ok")
|
||||
})
|
||||
// /funcs/<namespace> — без токена, namespace виден в URL всех функций
|
||||
http.HandleFunc("/funcs/", handler(operatorURL, externalURL, serviceToken, exclude))
|
||||
|
||||
log.Printf("sless-funcs-service listening on :%s (operator: %s)", port, operatorURL)
|
||||
log.Fatal(http.ListenAndServe(":"+port, nil))
|
||||
}
|
||||
|
||||
func handler(operatorURL, externalURL string, exclude map[string]bool) http.HandlerFunc {
|
||||
func handler(operatorURL, externalURL, serviceToken string, exclude map[string]bool) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path != "/funcs" {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
var namespace, authHeader string
|
||||
|
||||
token := strings.TrimPrefix(r.Header.Get("Authorization"), "Bearer ")
|
||||
if token == "" {
|
||||
http.Error(w, "Authorization: Bearer <token> required\n", http.StatusUnauthorized)
|
||||
return
|
||||
// Вариант 1: /funcs/<namespace> — namespace прямо в URL, токен не нужен.
|
||||
// Namespace виден в URL каждой функции — не является секретом.
|
||||
if strings.HasPrefix(r.URL.Path, "/funcs/") {
|
||||
namespace = strings.TrimPrefix(r.URL.Path, "/funcs/")
|
||||
namespace = strings.Trim(namespace, "/")
|
||||
if namespace == "" {
|
||||
http.Error(w, "usage: /funcs/<namespace>\n", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
// Используем serviceToken сервиса, если задан
|
||||
if serviceToken != "" {
|
||||
authHeader = "Bearer " + serviceToken
|
||||
}
|
||||
// authHeader может остаться пустым — оператор ответит 401 если не настроен
|
||||
} else {
|
||||
// Вариант 2: ?token=<jwt> — токен как query param (можно bookmarkнуть URL)
|
||||
token := r.URL.Query().Get("token")
|
||||
// Вариант 3: Authorization: Bearer <jwt> — стандартный заголовок
|
||||
if token == "" {
|
||||
token = strings.TrimPrefix(r.Header.Get("Authorization"), "Bearer ")
|
||||
}
|
||||
if token == "" {
|
||||
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
fmt.Fprintf(w, "Укажите namespace в URL или передайте токен:\n\n")
|
||||
fmt.Fprintf(w, " %s/funcs/<namespace>\n", externalURL)
|
||||
fmt.Fprintf(w, " %s/funcs?token=<jwt>\n", externalURL)
|
||||
return
|
||||
}
|
||||
sub, err := subFromJWT(token)
|
||||
if err != nil {
|
||||
http.Error(w, fmt.Sprintf("invalid token: %s\n", err), http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
namespace = namespaceFromSub(sub)
|
||||
authHeader = "Bearer " + token
|
||||
}
|
||||
|
||||
sub, err := subFromJWT(token)
|
||||
if err != nil {
|
||||
http.Error(w, fmt.Sprintf("invalid token: %s\n", err), http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
namespace := namespaceFromSub(sub)
|
||||
authHeader := "Bearer " + token
|
||||
|
||||
fns, err := apiGet[[]fnResponse](operatorURL+"/v1/namespaces/"+namespace+"/functions", authHeader)
|
||||
if err != nil {
|
||||
http.Error(w, fmt.Sprintf("operator error (functions): %s\n", err), http.StatusBadGateway)
|
||||
|
||||
Reference in New Issue
Block a user