Skip to content
Permalink
Browse files

api: input validation

It is necessary to validate the inputs before storing/using them
further in the code. This patch adds validation functions to all the
request structures defined in pkg/glusterfs/api/types.go . These
validation functions are then provided to a validation package that can
be called on a message buffer containing unmarshalled structure from
JSON input.

Signed-off-by: Raghavendra Talur <rtalur@redhat.com>
Reviewed-by: John Mulligan <jmulligan@redhat.com>
Reviewed-by: Michael Adam <obnox@redhat.com>
  • Loading branch information...
raghavendra-talur authored and obnoxxx committed Dec 18, 2017
1 parent 9cccea2 commit 787bae461b23003a4daa4d1d639016a754cf6b00
Showing with 171 additions and 4 deletions.
  1. +13 −0 apps/glusterfs/app_device.go
  2. +13 −0 apps/glusterfs/app_node.go
  3. +12 −0 apps/glusterfs/app_volume.go
  4. +2 −2 executors/sshexec/device.go
  5. +8 −2 glide.lock
  6. +2 −0 glide.yaml
  7. +121 −0 pkg/glusterfs/api/types.go
@@ -28,6 +28,13 @@ func (a *App) DeviceAdd(w http.ResponseWriter, r *http.Request) {
return
}

err = msg.Validate()
if err != nil {
http.Error(w, "validation failed: "+err.Error(), http.StatusBadRequest)
logger.LogError("validation failed: " + err.Error())
return
}

// Check the message has devices
if msg.Name == "" {
http.Error(w, "no devices added", http.StatusBadRequest)
@@ -323,6 +330,12 @@ func (a *App) DeviceSetState(w http.ResponseWriter, r *http.Request) {
http.Error(w, "request unable to be parsed", 422)
return
}
err = msg.Validate()
if err != nil {
http.Error(w, "validation failed: "+err.Error(), http.StatusBadRequest)
logger.LogError("validation failed: " + err.Error())
return
}

// Check for valid id, return immediately if not valid
err = a.db.View(func(tx *bolt.Tx) error {
@@ -28,6 +28,13 @@ func (a *App) NodeAdd(w http.ResponseWriter, r *http.Request) {
return
}

err = msg.Validate()
if err != nil {
http.Error(w, "validation failed: "+err.Error(), http.StatusBadRequest)
logger.LogError("validation failed: " + err.Error())
return
}

// Check information in JSON request
if len(msg.Hostnames.Manage) == 0 {
http.Error(w, "Manage hostname missing", http.StatusBadRequest)
@@ -330,6 +337,12 @@ func (a *App) NodeSetState(w http.ResponseWriter, r *http.Request) {
http.Error(w, "request unable to be parsed", 422)
return
}
err = msg.Validate()
if err != nil {
http.Error(w, "validation failed: "+err.Error(), http.StatusBadRequest)
logger.LogError("validation failed: " + err.Error())
return
}

// Check state is supported
err = a.db.View(func(tx *bolt.Tx) error {
@@ -34,6 +34,12 @@ func (a *App) VolumeCreate(w http.ResponseWriter, r *http.Request) {
http.Error(w, "request unable to be parsed", 422)
return
}
err = msg.Validate()
if err != nil {
http.Error(w, "validation failed: "+err.Error(), http.StatusBadRequest)
logger.LogError("validation failed: " + err.Error())
return
}

switch {
case msg.Gid < 0:
@@ -287,6 +293,12 @@ func (a *App) VolumeExpand(w http.ResponseWriter, r *http.Request) {
return
}
logger.Debug("Msg: %v", msg)
err = msg.Validate()
if err != nil {
http.Error(w, "validation failed: "+err.Error(), http.StatusBadRequest)
logger.LogError("validation failed: " + err.Error())
return
}

if msg.Size < 1 {
http.Error(w, "Invalid volume size", http.StatusBadRequest)
@@ -33,7 +33,7 @@ func (s *SshExecutor) DeviceSetup(host, device, vgid string) (d *executors.Devic

// Setup commands
commands := []string{
fmt.Sprintf("pvcreate --metadatasize=128M --dataalignment=256K %v", device),
fmt.Sprintf("pvcreate --metadatasize=128M --dataalignment=256K '%v'", device),
fmt.Sprintf("vgcreate %v %v", s.vgName(vgid), device),
}

@@ -65,7 +65,7 @@ func (s *SshExecutor) DeviceTeardown(host, device, vgid string) error {
// Setup commands
commands := []string{
fmt.Sprintf("vgremove %v", s.vgName(vgid)),
fmt.Sprintf("pvremove %v", device),
fmt.Sprintf("pvremove '%v'", device),
}

// Execute command

Some generated files are not rendered by default. Learn more.

@@ -22,3 +22,5 @@ import:
- ssh/agent
- package: k8s.io/client-go
version: v3.0.0-beta.0
- package: github.com/go-ozzo/ozzo-validation
version: v3.3
@@ -18,9 +18,36 @@ package api

import (
"fmt"
"regexp"
"sort"

"github.com/go-ozzo/ozzo-validation"
"github.com/go-ozzo/ozzo-validation/is"
)

var (
// Restricting the deviceName to much smaller subset of Unix Path
// as unix path takes almost everything except NULL
deviceNameRe = regexp.MustCompile("^/[a-zA-Z0-9_./-]+$")

// Volume name constraints decided by looking at
// "cli_validate_volname" function in cli-cmd-parser.c of gluster code
volumeNameRe = regexp.MustCompile("^[a-zA-Z0-9_-]+$")

blockVolNameRe = regexp.MustCompile("^[a-zA-Z0-9_-]+$")
)

// ValidateUUID is written this way because heketi UUID does not
// conform to neither UUID v4 nor v5.
func ValidateUUID(value interface{}) error {
s, _ := value.(string)
err := validation.Validate(s, validation.RuneLength(32, 32), is.Hexadecimal)
if err != nil {
return fmt.Errorf("not a valid UUID")
}
return nil
}

// State
type EntryState string

@@ -31,6 +58,15 @@ const (
EntryStateFailed EntryState = "failed"
)

func ValidateEntryState(value interface{}) error {
s, _ := value.(string)
err := validation.Validate(s, validation.Required, validation.In(EntryStateOnline, EntryStateOffline, EntryStateFailed))
if err != nil {
return fmt.Errorf("state requested is not valid")
}
return nil
}

type DurabilityType string

const (
@@ -39,11 +75,26 @@ const (
DurabilityEC DurabilityType = "disperse"
)

func ValidateDurabilityType(value interface{}) error {
s, _ := value.(string)
err := validation.Validate(s, validation.Required, validation.In(DurabilityReplicate, DurabilityDistributeOnly, DurabilityEC))
if err != nil {
return fmt.Errorf("durability type requested is not valid")
}
return nil
}

// Common
type StateRequest struct {
State EntryState `json:"state"`
}

func (statereq StateRequest) Validate() error {
return validation.ValidateStruct(&statereq,
validation.Field(&statereq.State, validation.Required, validation.By(ValidateEntryState)),
)
}

// Storage values in KB
type StorageSize struct {
Total uint64 `json:"total"`
@@ -56,6 +107,35 @@ type HostAddresses struct {
Storage sort.StringSlice `json:"storage"`
}

func ValidateManagementHostname(value interface{}) error {
s, _ := value.(sort.StringSlice)
for _, fqdn := range s {
err := validation.Validate(fqdn, validation.Required, is.Host)
if err != nil {
return fmt.Errorf("Manage hostname should be valid hostname")
}
}
return nil
}

func ValidateStorageHostname(value interface{}) error {
s, _ := value.(sort.StringSlice)
for _, ip := range s {
err := validation.Validate(ip, validation.Required, is.Host)
if err != nil {
return fmt.Errorf("Storage hostname should be valid IP")
}
}
return nil
}

func (hostadd HostAddresses) Validate() error {
return validation.ValidateStruct(&hostadd,
validation.Field(&hostadd.Manage, validation.Required, validation.By(ValidateManagementHostname)),
validation.Field(&hostadd.Storage, validation.Required, validation.By(ValidateStorageHostname)),
)
}

// Brick
type BrickInfo struct {
Id string `json:"id"`
@@ -73,11 +153,24 @@ type Device struct {
Name string `json:"name"`
}

func (dev Device) Validate() error {
return validation.ValidateStruct(&dev,
validation.Field(&dev.Name, validation.Required, validation.Match(deviceNameRe)),
)
}

type DeviceAddRequest struct {
Device
NodeId string `json:"node"`
}

func (devAddReq DeviceAddRequest) Validate() error {
return validation.ValidateStruct(&devAddReq,
validation.Field(&devAddReq.Device, validation.Required),
validation.Field(&devAddReq.NodeId, validation.Required, validation.By(ValidateUUID)),
)
}

type DeviceInfo struct {
Device
Storage StorageSize `json:"storage"`
@@ -97,6 +190,14 @@ type NodeAddRequest struct {
ClusterId string `json:"cluster"`
}

func (req NodeAddRequest) Validate() error {
return validation.ValidateStruct(&req,
validation.Field(&req.Zone, validation.Required, validation.Min(1)),
validation.Field(&req.Hostnames, validation.Required),
validation.Field(&req.ClusterId, validation.Required, validation.By(ValidateUUID)),
)
}

type NodeInfo struct {
NodeAddRequest
Id string `json:"id"`
@@ -160,6 +261,20 @@ type VolumeCreateRequest struct {
} `json:"snapshot"`
}

func (volCreateRequest VolumeCreateRequest) Validate() error {
return validation.ValidateStruct(&volCreateRequest,
validation.Field(&volCreateRequest.Size, validation.Required, validation.Min(1)),
validation.Field(&volCreateRequest.Clusters, validation.By(ValidateUUID)),
validation.Field(&volCreateRequest.Name, validation.Match(volumeNameRe)),
validation.Field(&volCreateRequest.Durability, validation.Skip),
validation.Field(&volCreateRequest.Gid, validation.Skip),
validation.Field(&volCreateRequest.GlusterVolumeOptions, validation.Skip),
// This is possibly a bug in validation lib, ignore next two lines for now
// validation.Field(&volCreateRequest.Snapshot.Enable, validation.In(true, false)),
// validation.Field(&volCreateRequest.Snapshot.Factor, validation.Min(1.0)),
)
}

type VolumeInfo struct {
VolumeCreateRequest
Id string `json:"id"`
@@ -186,6 +301,12 @@ type VolumeExpandRequest struct {
Size int `json:"expand_size"`
}

func (volExpandReq VolumeExpandRequest) Validate() error {
return validation.ValidateStruct(&volExpandReq,
validation.Field(&volExpandReq.Size, validation.Required, validation.Min(1)),
)
}

// Constructors

func NewVolumeInfoResponse() *VolumeInfoResponse {

0 comments on commit 787bae4

Please sign in to comment.
You can’t perform that action at this time.