diff --git a/internal/controller/api_service_accounts.go b/internal/controller/api_service_accounts.go index ccb2c38..f292fc7 100644 --- a/internal/controller/api_service_accounts.go +++ b/internal/controller/api_service_accounts.go @@ -4,6 +4,7 @@ import ( "errors" storepkg "github.com/cirruslabs/orchard/internal/controller/store" "github.com/cirruslabs/orchard/internal/responder" + "github.com/cirruslabs/orchard/internal/simplename" v1 "github.com/cirruslabs/orchard/pkg/resource/v1" "github.com/gin-gonic/gin" "github.com/google/uuid" @@ -23,7 +24,11 @@ func (controller *Controller) createServiceAccount(ctx *gin.Context) responder.R } if serviceAccount.Name == "" { - return responder.JSON(http.StatusPreconditionFailed, NewErrorResponse("service account name is empty")) + return responder.JSON(http.StatusPreconditionFailed, + NewErrorResponse("service account name is empty")) + } else if err := simplename.Validate(serviceAccount.Name); err != nil { + return responder.JSON(http.StatusPreconditionFailed, + NewErrorResponse("service account %v", err)) } // validate roles diff --git a/internal/controller/api_vms.go b/internal/controller/api_vms.go index 584b6ed..5a81f47 100644 --- a/internal/controller/api_vms.go +++ b/internal/controller/api_vms.go @@ -4,6 +4,7 @@ import ( "errors" storepkg "github.com/cirruslabs/orchard/internal/controller/store" "github.com/cirruslabs/orchard/internal/responder" + "github.com/cirruslabs/orchard/internal/simplename" "github.com/cirruslabs/orchard/pkg/resource/v1" "github.com/gin-gonic/gin" "github.com/google/uuid" @@ -25,6 +26,9 @@ func (controller *Controller) createVM(ctx *gin.Context) responder.Responder { if vm.Name == "" { return responder.JSON(http.StatusPreconditionFailed, NewErrorResponse("VM name is empty")) + } else if err := simplename.Validate(vm.Name); err != nil { + return responder.JSON(http.StatusPreconditionFailed, + NewErrorResponse("VM name %v", err)) } if vm.Image == "" { return responder.JSON(http.StatusPreconditionFailed, NewErrorResponse("VM image is empty")) diff --git a/internal/controller/api_workers.go b/internal/controller/api_workers.go index cf0bba3..c3c9efb 100644 --- a/internal/controller/api_workers.go +++ b/internal/controller/api_workers.go @@ -4,6 +4,7 @@ import ( "errors" storepkg "github.com/cirruslabs/orchard/internal/controller/store" "github.com/cirruslabs/orchard/internal/responder" + "github.com/cirruslabs/orchard/internal/simplename" v1 "github.com/cirruslabs/orchard/pkg/resource/v1" "github.com/gin-gonic/gin" "net/http" @@ -23,6 +24,9 @@ func (controller *Controller) createWorker(ctx *gin.Context) responder.Responder if worker.Name == "" { return responder.JSON(http.StatusPreconditionFailed, NewErrorResponse("worker name is empty")) + } else if err := simplename.Validate(worker.Name); err != nil { + return responder.JSON(http.StatusPreconditionFailed, + NewErrorResponse("worker name %v", err)) } currentTime := time.Now() diff --git a/internal/simplename/simplename.go b/internal/simplename/simplename.go new file mode 100644 index 0000000..5c42136 --- /dev/null +++ b/internal/simplename/simplename.go @@ -0,0 +1,31 @@ +package simplename + +import ( + "errors" +) + +var ErrNotASimpleName = errors.New("name contains restricted characters, please only use [A-Za-z0-9:-_.]") + +func Validate(s string) error { + for _, ch := range s { + if ch >= 'a' && ch <= 'z' { + continue + } + + if ch >= 'A' && ch <= 'Z' { + continue + } + + if ch >= '0' && ch <= '9' { + continue + } + + if ch == ':' || ch == '-' || ch == '_' || ch == '.' { + continue + } + + return ErrNotASimpleName + } + + return nil +} diff --git a/internal/simplename/simplename_test.go b/internal/simplename/simplename_test.go new file mode 100644 index 0000000..8c36821 --- /dev/null +++ b/internal/simplename/simplename_test.go @@ -0,0 +1,17 @@ +package simplename_test + +import ( + "github.com/cirruslabs/orchard/internal/simplename" + "github.com/stretchr/testify/require" + "testing" +) + +func TestValidate(t *testing.T) { + require.NoError(t, simplename.Validate("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz:-_.")) + require.NoError(t, simplename.Validate("vm-1")) + require.NoError(t, simplename.Validate("vm_2")) + require.NoError(t, simplename.Validate("host.local")) + + require.Error(t, simplename.Validate("vm%"), "special characters") + require.Error(t, simplename.Validate("😐"), "non-ASCII characters") +} diff --git a/pkg/client/service_accounts.go b/pkg/client/service_accounts.go index 0bcea53..f25076d 100644 --- a/pkg/client/service_accounts.go +++ b/pkg/client/service_accounts.go @@ -5,6 +5,7 @@ import ( "fmt" "github.com/cirruslabs/orchard/pkg/resource/v1" "net/http" + "net/url" ) type ServiceAccountsService struct { @@ -36,7 +37,8 @@ func (service *ServiceAccountsService) List(ctx context.Context) ([]v1.ServiceAc func (service *ServiceAccountsService) Get(ctx context.Context, name string) (*v1.ServiceAccount, error) { var serviceAccount v1.ServiceAccount - err := service.client.request(ctx, http.MethodGet, fmt.Sprintf("service-accounts/%s", name), + err := service.client.request(ctx, http.MethodGet, + fmt.Sprintf("service-accounts/%s", url.PathEscape(name)), nil, &serviceAccount, nil) if err != nil { return nil, err @@ -46,7 +48,8 @@ func (service *ServiceAccountsService) Get(ctx context.Context, name string) (*v } func (service *ServiceAccountsService) Update(ctx context.Context, serviceAccount *v1.ServiceAccount) error { - err := service.client.request(ctx, http.MethodPut, fmt.Sprintf("service-accounts/%s", serviceAccount.Name), + err := service.client.request(ctx, http.MethodPut, + fmt.Sprintf("service-accounts/%s", url.PathEscape(serviceAccount.Name)), serviceAccount, nil, nil) if err != nil { return err @@ -62,7 +65,8 @@ func (service *ServiceAccountsService) Delete(ctx context.Context, name string, params["force"] = "true" } - err := service.client.request(ctx, http.MethodDelete, fmt.Sprintf("service-accounts/%s", name), + err := service.client.request(ctx, http.MethodDelete, + fmt.Sprintf("service-accounts/%s", url.PathEscape(name)), nil, nil, params) if err != nil { return err diff --git a/pkg/client/vms.go b/pkg/client/vms.go index fc3ef74..2dcbe9e 100644 --- a/pkg/client/vms.go +++ b/pkg/client/vms.go @@ -6,6 +6,7 @@ import ( "github.com/cirruslabs/orchard/pkg/resource/v1" "net" "net/http" + "net/url" "strconv" ) @@ -57,7 +58,7 @@ func (service *VMsService) List(ctx context.Context) ([]v1.VM, error) { func (service *VMsService) Get(ctx context.Context, name string) (*v1.VM, error) { var vm v1.VM - err := service.client.request(ctx, http.MethodGet, fmt.Sprintf("vms/%s", name), + err := service.client.request(ctx, http.MethodGet, fmt.Sprintf("vms/%s", url.PathEscape(name)), nil, &vm, nil) if err != nil { return nil, err @@ -68,7 +69,7 @@ func (service *VMsService) Get(ctx context.Context, name string) (*v1.VM, error) func (service *VMsService) Update(ctx context.Context, vm v1.VM) (*v1.VM, error) { var updatedVM v1.VM - err := service.client.request(ctx, http.MethodPut, fmt.Sprintf("vms/%s", vm.Name), + err := service.client.request(ctx, http.MethodPut, fmt.Sprintf("vms/%s", url.PathEscape(vm.Name)), vm, &updatedVM, nil) if err != nil { return &updatedVM, err @@ -78,7 +79,7 @@ func (service *VMsService) Update(ctx context.Context, vm v1.VM) (*v1.VM, error) } func (service *VMsService) Delete(ctx context.Context, name string) error { - err := service.client.request(ctx, http.MethodDelete, fmt.Sprintf("vms/%s", name), + err := service.client.request(ctx, http.MethodDelete, fmt.Sprintf("vms/%s", url.PathEscape(name)), nil, nil, nil) if err != nil { return err @@ -93,7 +94,7 @@ func (service *VMsService) PortForward( port uint16, waitSeconds uint16, ) (net.Conn, error) { - return service.client.wsRequest(ctx, fmt.Sprintf("vms/%s/port-forward", name), + return service.client.wsRequest(ctx, fmt.Sprintf("vms/%s/port-forward", url.PathEscape(name)), map[string]string{ "port": strconv.FormatUint(uint64(port), 10), "wait": strconv.FormatUint(uint64(waitSeconds), 10), @@ -101,12 +102,12 @@ func (service *VMsService) PortForward( } func (service *VMsService) StreamEvents(name string) *EventStreamer { - return NewEventStreamer(service.client, fmt.Sprintf("vms/%s/events", name)) + return NewEventStreamer(service.client, fmt.Sprintf("vms/%s/events", url.PathEscape(name))) } func (service *VMsService) Logs(ctx context.Context, name string) (lines []string, err error) { var events []v1.Event - err = service.client.request(ctx, http.MethodGet, fmt.Sprintf("vms/%s/events", name), + err = service.client.request(ctx, http.MethodGet, fmt.Sprintf("vms/%s/events", url.PathEscape(name)), nil, &events, nil) if err != nil { return diff --git a/pkg/client/workers.go b/pkg/client/workers.go index 058b6e2..a85bede 100644 --- a/pkg/client/workers.go +++ b/pkg/client/workers.go @@ -5,6 +5,7 @@ import ( "fmt" "github.com/cirruslabs/orchard/pkg/resource/v1" "net/http" + "net/url" ) type WorkersService struct { @@ -36,7 +37,7 @@ func (service *WorkersService) List(ctx context.Context) ([]v1.Worker, error) { func (service *WorkersService) Get(ctx context.Context, name string) (*v1.Worker, error) { var worker v1.Worker - err := service.client.request(ctx, http.MethodGet, fmt.Sprintf("workers/%s", name), + err := service.client.request(ctx, http.MethodGet, fmt.Sprintf("workers/%s", url.PathEscape(name)), nil, &worker, nil) if err != nil { return nil, err @@ -46,7 +47,7 @@ func (service *WorkersService) Get(ctx context.Context, name string) (*v1.Worker } func (service *WorkersService) Update(ctx context.Context, worker v1.Worker) (*v1.Worker, error) { - err := service.client.request(ctx, http.MethodPut, fmt.Sprintf("workers/%s", worker.Name), + err := service.client.request(ctx, http.MethodPut, fmt.Sprintf("workers/%s", url.PathEscape(worker.Name)), worker, &worker, nil) if err != nil { return nil, err @@ -56,7 +57,7 @@ func (service *WorkersService) Update(ctx context.Context, worker v1.Worker) (*v } func (service *WorkersService) Delete(ctx context.Context, name string) error { - err := service.client.request(ctx, http.MethodDelete, fmt.Sprintf("workers/%s", name), + err := service.client.request(ctx, http.MethodDelete, fmt.Sprintf("workers/%s", url.PathEscape(name)), nil, nil, nil) if err != nil { return err