add more name verifications

This commit is contained in:
d34dscene
2024-08-29 19:01:03 +02:00
parent 10bfd38c81
commit 1898e81886
8 changed files with 98 additions and 50 deletions

View File

@@ -5,8 +5,8 @@ services:
image: ghcr.io/mizuchilabs/mantrae:latest
container_name: mantrae
command:
- --url=http://traefik:8080
- --username=admin
- --url=http://traefik:8080 # if traefik is running on the same host
- --username=admin # use if you want to enable basicauth on traefik
- --password=admin
ports:
- 3000:3000
@@ -25,7 +25,7 @@ services:
- ./traefik:/etc/traefik
- /var/run/docker.sock:/var/run/docker.sock
environment:
- CF_DNS_API_TOKEN=xxx
- CF_DNS_API_TOKEN=xxx # Set cloudflare token
networks:
- proxy
command:
@@ -37,6 +37,7 @@ services:
- --entrypoints.web.http.redirections.entryPoint.scheme=https
- --entrypoints.websecure.address=:443
- --entrypoints.websecure.asDefault=true
- --entrypoints.websecure.http.tls.certresolver=letsencrypt # Set the default cert resolver
- --serversTransport.insecureSkipVerify=true
- --providers.docker=true
- --providers.docker.exposedByDefault=false
@@ -48,7 +49,7 @@ services:
- --certificatesresolvers.letsencrypt.acme.dnschallenge.delaybeforecheck=0
labels:
- traefik.enable=true
- traefik.http.routers.dashboard.rule=PathPrefix(`/api`) || PathPrefix(`/dashboard`)
- traefik.http.routers.dashboard.rule=Host(`traefik.example.com`) && (PathPrefix(`/api`) || PathPrefix(`/dashboard`))
- traefik.http.routers.dashboard.service=api@internal
- traefik.http.routers.dashboard.entrypoints=websecure
restart: always

View File

@@ -351,6 +351,7 @@ func UpdateMiddleware(w http.ResponseWriter, r *http.Request) {
}
updateName(profile.Dynamic.Middlewares, middlewareName, middleware.Name)
if err := middleware.Verify(); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return

View File

@@ -123,7 +123,7 @@ func getRouters[T Routerable](p Profile, endpoint string) map[string]Router {
return nil
}
routers := make(map[string]Router)
routers := make(map[string]Router, len(routerables))
for _, r := range routerables {
newRouter := r.ToRouter()
routers[newRouter.Name] = *newRouter
@@ -216,7 +216,7 @@ func getServices[T Serviceable](p Profile, endpoint string) map[string]Service {
return nil
}
services := make(map[string]Service)
services := make(map[string]Service, len(serviceables))
for _, s := range serviceables {
newService := s.ToService()
services[newService.Name] = *newService
@@ -319,7 +319,7 @@ func getMiddlewares[T Middlewareable](p Profile, endpoint string) map[string]Mid
return nil
}
middlewares := make(map[string]Middleware)
middlewares := make(map[string]Middleware, len(middlewareables))
for _, m := range middlewareables {
newMiddleware := m.ToMiddleware()
middlewares[newMiddleware.Name] = *newMiddleware
@@ -346,7 +346,10 @@ func GetTraefikConfig() {
getRouters[HTTPRouter](profile, HTTPRouterAPI),
getRouters[TCPRouter](profile, TCPRouterAPI),
getRouters[UDPRouter](profile, UDPRouterAPI),
profile.Dynamic.Routers,
filterByLocalProvider(
profile.Dynamic.Routers,
func(r Router) string { return r.Provider },
),
)
// Retrieve services
@@ -354,14 +357,20 @@ func GetTraefikConfig() {
getServices[HTTPService](profile, HTTPServiceAPI),
getServices[TCPService](profile, TCPServiceAPI),
getServices[UDPService](profile, UDPServiceAPI),
profile.Dynamic.Services,
filterByLocalProvider(
profile.Dynamic.Services,
func(s Service) string { return s.Provider },
),
)
// Fetch middlewares
d.Middlewares = merge(
getMiddlewares[HTTPMiddleware](profile, HTTPMiddlewaresAPI),
getMiddlewares[TCPMiddleware](profile, TCPMiddlewaresAPI),
profile.Dynamic.Middlewares,
filterByLocalProvider(
profile.Dynamic.Middlewares,
func(m Middleware) string { return m.Provider },
),
)
// Retrieve entrypoints
@@ -414,6 +423,17 @@ func Sync() {
}
}
// Filter http provider
func filterByLocalProvider[T any](items map[string]T, getProvider func(T) string) map[string]T {
filteredItems := make(map[string]T)
for key, item := range items {
if getProvider(item) == "http" {
filteredItems[key] = item
}
}
return filteredItems
}
func merge[T any](maps ...map[string]T) map[string]T {
merged := make(map[string]T)
for _, m := range maps {

View File

@@ -29,17 +29,22 @@ func (p *Profile) Verify() error {
func (r *Router) Verify() error {
if r.Name == "" {
return fmt.Errorf("router name cannot be empty")
}
if r.Service == "" {
return fmt.Errorf("service cannot be empty")
}
if r.RouterType == "" {
return fmt.Errorf("router type cannot be empty")
return fmt.Errorf("name cannot be empty")
}
if r.Rule == "" && r.RouterType != "udp" {
return fmt.Errorf("rule cannot be empty")
}
if r.RouterType == "" {
r.RouterType = "http"
}
if r.Provider == "" {
r.Provider = "http"
}
if r.Service == "" {
r.Service = r.Name
}
r.Name = validateName(r.Name, r.Provider)
r.Service = validateName(r.Name, r.Provider)
return nil
}
@@ -48,8 +53,12 @@ func (s *Service) Verify() error {
return fmt.Errorf("service name cannot be empty")
}
if s.ServiceType == "" {
return fmt.Errorf("service type cannot be empty")
s.ServiceType = "http"
}
if s.Provider == "" {
s.Provider = "http"
}
s.Name = validateName(s.Name, s.Provider)
return nil
}
@@ -58,11 +67,12 @@ func (m *Middleware) Verify() error {
return fmt.Errorf("middleware name cannot be empty")
}
if m.Provider == "" {
return fmt.Errorf("provider cannot be empty")
m.Provider = "http"
}
if m.Type == "" {
return fmt.Errorf("type cannot be empty")
}
m.Name = validateName(m.Name, m.Provider)
// Hashes the password strings in the middleware
if m.BasicAuth != nil {
@@ -120,6 +130,19 @@ func (m *Middleware) Verify() error {
return nil
}
func validateName(s, p string) string {
name := strings.ToLower(s)
parts := strings.Split(name, "@")
if len(parts) > 1 {
name = parts[0] + "@" + parts[1]
} else {
name = parts[0] + "@" + p
}
return name
}
func isValidURL(u string) bool {
// If no scheme is provided, prepend "http://"
if !strings.Contains(u, "://") {

View File

@@ -125,16 +125,22 @@ export async function deleteProfile(name: string): Promise<void> {
export async function updateRouter(
oldName: string,
router: Router,
service: Service | undefined
service: Service
): Promise<void> {
if (!get(profile)) return;
if (service === undefined) service = newService();
service.name = router.service + '@' + router.provider;
service.serviceType = router.routerType;
await handleRequest(`/routers/${get(profile).name}/${oldName}`, 'PUT', router);
const response = await handleRequest(`/services/${get(profile).name}/${oldName}`, 'PUT', service);
profile.set(response);
toast.success(`Router ${router.name} updated`);
const resRouter = await handleRequest(`/routers/${get(profile).name}/${oldName}`, 'PUT', router);
if (resRouter) {
const resService = await handleRequest(
`/services/${get(profile).name}/${oldName}`,
'PUT',
service
);
if (resService) {
profile.set(resService);
toast.success(`Router ${router.name} updated`);
}
}
}
export async function deleteRouter(name: string): Promise<void> {

View File

@@ -16,10 +16,8 @@
let service = newService();
const create = async () => {
if (router.service === '' || isNameTaken) return;
router.name = router.service + '@' + router.provider;
service.name = router.service + '@' + router.provider;
service.serviceType = router.routerType;
if (router.name === '' || isNameTaken) return;
service.name = router.name.split('@')[0] + '@' + router.provider;
await updateRouter(router.name, router, service);
router = newRouter();
@@ -59,7 +57,9 @@
// Check if router name is taken
let isNameTaken = false;
$: isNameTaken = $routers.some((r) => r.service === router.service);
$: isNameTaken = $routers.some(
(r) => r.name.split('@')[0].toLowerCase() === router.name.split('@')[0].toLowerCase()
);
</script>
<Dialog.Root>
@@ -105,7 +105,7 @@
id="name"
name="name"
type="text"
bind:value={router.service}
bind:value={router.name}
class={isNameTaken
? 'col-span-3 border-red-400 focus-visible:ring-0 focus-visible:ring-offset-0'
: 'col-span-3 focus-visible:ring-0 focus-visible:ring-offset-0'}

View File

@@ -14,21 +14,15 @@
import Service from '../forms/service.svelte';
export let router: Router;
let service = getService(router.name);
let service = getService(router.name) ?? newService();
let routerCompare = $routers.filter((r) => r.name !== router.name);
let open = false;
const update = () => {
if (service === undefined) {
service = newService();
service.serviceType = router.routerType;
}
if (router.service === '' || isNameTaken) return;
let oldName = router.name;
router.name = router.service + '@' + router.provider;
service.name = router.service + '@' + router.provider; // Extra check in case router name changed
service.serviceType = router.routerType;
updateRouter(oldName, router, service);
if (router.name === '' || isNameTaken) return;
// Extra check in case router name changed
service.name = router.name.split('@')[0] + '@' + router.provider;
updateRouter(router.service, router, service);
open = false;
};
@@ -55,7 +49,9 @@
// Check if router name is taken unless self
let isNameTaken = false;
$: isNameTaken = routerCompare.some((r) => r.service === router.service);
$: isNameTaken = routerCompare.some(
(r) => r.name.split('@')[0].toLowerCase() === router.name.split('@')[0].toLowerCase()
);
const onKeydown = (e: KeyboardEvent) => {
if (e.key === 'Enter') {
@@ -104,7 +100,7 @@
class={isNameTaken
? 'col-span-3 border-red-400 focus-visible:ring-0 focus-visible:ring-offset-0'
: 'col-span-3 focus-visible:ring-0 focus-visible:ring-offset-0'}
bind:value={router.service}
bind:value={router.name}
placeholder="Name of the router"
on:keydown={onKeydown}
required

View File

@@ -10,7 +10,8 @@
middlewares,
routers,
services,
updateRouter
updateRouter,
getService
} from '$lib/api';
import CreateRouter from '$lib/components/modals/createRouter.svelte';
import UpdateRouter from '$lib/components/modals/updateRouter.svelte';
@@ -84,13 +85,13 @@
const toggleEntrypoint = (router: Router, item: Selected<unknown>[] | undefined) => {
if (item === undefined) return;
router.entrypoints = item.map((i) => i.value) as string[];
let service = $profile?.dynamic?.services?.[router.name];
let service = getService(router.name);
updateRouter(router.name, router, service);
};
const toggleMiddleware = (router: Router, item: Selected<unknown>[] | undefined) => {
if (item === undefined) return;
router.middlewares = item.map((i) => i.value) as string[];
let service = $profile?.dynamic?.services?.[router.name];
let service = getService(router.name);
updateRouter(router.name, router, service);
};
const getSelectedEntrypoints = (router: Router): Selected<unknown>[] => {
@@ -213,7 +214,7 @@
{#each fRouters as router}
<Table.Row>
<Table.Cell class={showColumn('name') ? 'font-medium' : 'hidden'}>
{router.service}
{router.name.split('@')[0]}
</Table.Cell>
<Table.Cell class={showColumn('provider') ? 'font-medium' : 'hidden'}>
<span