github.com/david-imola/snapd@v0.0.0-20210611180407-2de8ddeece6d/snap/naming/validate.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2018-2021 Canonical Ltd
     5   *
     6   * This program is free software: you can redistribute it and/or modify
     7   * it under the terms of the GNU General Public License version 3 as
     8   * published by the Free Software Foundation.
     9   *
    10   * This program is distributed in the hope that it will be useful,
    11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13   * GNU General Public License for more details.
    14   *
    15   * You should have received a copy of the GNU General Public License
    16   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17   *
    18   */
    19  
    20  // Package naming implements naming constraints and concepts for snaps and their elements.
    21  package naming
    22  
    23  import (
    24  	"fmt"
    25  	"regexp"
    26  	"strings"
    27  )
    28  
    29  // almostValidName is part of snap and socket name validation.
    30  //   the full regexp we could use, "^(?:[a-z0-9]+-?)*[a-z](?:-?[a-z0-9])*$", is
    31  //   O(2ⁿ) on the length of the string in python. An equivalent regexp that
    32  //   doesn't have the nested quantifiers that trip up Python's re would be
    33  //   "^(?:[a-z0-9]|(?<=[a-z0-9])-)*[a-z](?:[a-z0-9]|-(?=[a-z0-9]))*$", but Go's
    34  //   regexp package doesn't support look-aheads nor look-behinds, so in order to
    35  //   have a unified implementation in the Go and Python bits of the project
    36  //   we're doing it this way instead. Check the length (if applicable), check
    37  //   this regexp, then check the dashes.
    38  //   This still leaves sc_snap_name_validate (in cmd/snap-confine/snap.c) and
    39  //   snap_validate (cmd/snap-update-ns/bootstrap.c) with their own handcrafted
    40  //   validators.
    41  var almostValidName = regexp.MustCompile("^[a-z0-9-]*[a-z][a-z0-9-]*$")
    42  
    43  // validInstanceKey is a regular expression describing a valid snap instance key
    44  var validInstanceKey = regexp.MustCompile("^[a-z0-9]{1,10}$")
    45  
    46  // isValidName checks snap and socket identifiers.
    47  func isValidName(name string) bool {
    48  	if !almostValidName.MatchString(name) {
    49  		return false
    50  	}
    51  	if name[0] == '-' || name[len(name)-1] == '-' || strings.Contains(name, "--") {
    52  		return false
    53  	}
    54  	return true
    55  }
    56  
    57  // ValidateInstance checks if a string can be used as a snap instance name.
    58  func ValidateInstance(instanceName string) error {
    59  	// NOTE: This function should be synchronized with the two other
    60  	// implementations: sc_instance_name_validate and validate_instance_name .
    61  	pos := strings.IndexByte(instanceName, '_')
    62  	if pos == -1 {
    63  		// just store name
    64  		return ValidateSnap(instanceName)
    65  	}
    66  
    67  	storeName := instanceName[:pos]
    68  	instanceKey := instanceName[pos+1:]
    69  	if err := ValidateSnap(storeName); err != nil {
    70  		return err
    71  	}
    72  	if !validInstanceKey.MatchString(instanceKey) {
    73  		return fmt.Errorf("invalid instance key: %q", instanceKey)
    74  	}
    75  	return nil
    76  }
    77  
    78  // ValidateSnap checks if a string can be used as a snap name.
    79  func ValidateSnap(name string) error {
    80  	// NOTE: This function should be synchronized with the two other
    81  	// implementations: sc_snap_name_validate and validate_snap_name .
    82  	if len(name) < 2 || len(name) > 40 || !isValidName(name) {
    83  		return fmt.Errorf("invalid snap name: %q", name)
    84  	}
    85  	return nil
    86  }
    87  
    88  // Regular expression describing correct plug, slot and interface names.
    89  var validPlugSlotIface = regexp.MustCompile("^[a-z](?:-?[a-z0-9])*$")
    90  
    91  // ValidatePlug checks if a string can be used as a slot name.
    92  //
    93  // Slot names and plug names within one snap must have unique names.
    94  // This is not enforced by this function but is enforced by snap-level
    95  // validation.
    96  func ValidatePlug(name string) error {
    97  	if !validPlugSlotIface.MatchString(name) {
    98  		return fmt.Errorf("invalid plug name: %q", name)
    99  	}
   100  	return nil
   101  }
   102  
   103  // ValidateSlot checks if a string can be used as a slot name.
   104  //
   105  // Slot names and plug names within one snap must have unique names.
   106  // This is not enforced by this function but is enforced by snap-level
   107  // validation.
   108  func ValidateSlot(name string) error {
   109  	if !validPlugSlotIface.MatchString(name) {
   110  		return fmt.Errorf("invalid slot name: %q", name)
   111  	}
   112  	return nil
   113  }
   114  
   115  // ValidateInterface checks if a string can be used as an interface name.
   116  func ValidateInterface(name string) error {
   117  	if !validPlugSlotIface.MatchString(name) {
   118  		return fmt.Errorf("invalid interface name: %q", name)
   119  	}
   120  	return nil
   121  }
   122  
   123  // Regular expressions describing correct identifiers.
   124  var validHook = regexp.MustCompile("^[a-z](?:-?[a-z0-9])*$")
   125  
   126  // ValidateHook checks if a string can be used as a hook name.
   127  func ValidateHook(name string) error {
   128  	valid := validHook.MatchString(name)
   129  	if !valid {
   130  		return fmt.Errorf("invalid hook name: %q", name)
   131  	}
   132  	return nil
   133  }
   134  
   135  // ValidAlias is a regular expression describing a valid alias
   136  var ValidAlias = regexp.MustCompile("^[a-zA-Z0-9][-_.a-zA-Z0-9]*$")
   137  
   138  // ValidateAlias checks if a string can be used as an alias name.
   139  func ValidateAlias(alias string) error {
   140  	valid := ValidAlias.MatchString(alias)
   141  	if !valid {
   142  		return fmt.Errorf("invalid alias name: %q", alias)
   143  	}
   144  	return nil
   145  }
   146  
   147  // ValidApp is a regular expression describing a valid application name
   148  var ValidApp = regexp.MustCompile("^[a-zA-Z0-9](?:-?[a-zA-Z0-9])*$")
   149  
   150  // ValidateApp tells whether a string is a valid application name.
   151  func ValidateApp(n string) error {
   152  	if !ValidApp.MatchString(n) {
   153  		return fmt.Errorf("invalid app name: %q", n)
   154  	}
   155  	return nil
   156  }
   157  
   158  // ValidateSockeName checks if a string ca be used as a name for a socket (for
   159  // socket activation).
   160  func ValidateSocket(name string) error {
   161  	if !isValidName(name) {
   162  		return fmt.Errorf("invalid socket name: %q", name)
   163  	}
   164  	return nil
   165  }
   166  
   167  // ValidSnapID is a regular expression describing a valid snapd-id
   168  var ValidSnapID = regexp.MustCompile("^[a-z0-9A-Z]{32}$")
   169  
   170  // ValidateSnapID checks whether the string is a valid snap-id.
   171  func ValidateSnapID(id string) error {
   172  	if !ValidSnapID.MatchString(id) {
   173  		return fmt.Errorf("invalid snap-id: %q", id)
   174  	}
   175  	return nil
   176  }
   177  
   178  // ValidateSecurityTag validates known variants of snap security tag.
   179  //
   180  // Two forms are recognised, one for apps and one for hooks. Other forms
   181  // are possible but are not handled here.
   182  //
   183  // TODO: handle the weird udev variant.
   184  func ValidateSecurityTag(tag string) error {
   185  	_, err := ParseSecurityTag(tag)
   186  	return err
   187  }
   188  
   189  // validQuotaGroupName is a regular expression describing a valid quota resource
   190  // group name. It is the same regular expression as a snap name
   191  var validQuotaGroupName = almostValidName
   192  
   193  // ValidateQuotaGroup checks if a string can be used as a name for a quota
   194  // resource group. Currently the rules are exactly the same as for snap names.
   195  // Higher levels might also reserve some names, that is not taken into
   196  // account by ValidateQuotaGroup itself.
   197  func ValidateQuotaGroup(grp string) error {
   198  	if grp == "" {
   199  		return fmt.Errorf("invalid quota group name: must not be empty")
   200  	}
   201  
   202  	if len(grp) < 2 || len(grp) > 40 {
   203  		return fmt.Errorf("invalid quota group name: must be between 2 and 40 characters long")
   204  	}
   205  
   206  	// check that the name matches the regexp
   207  	if !validQuotaGroupName.MatchString(grp) {
   208  		return fmt.Errorf("invalid quota group name: contains invalid characters (valid names start with a letter and are otherwise alphanumeric with dashes)")
   209  	}
   210  
   211  	if grp[0] == '-' || grp[len(grp)-1] == '-' || strings.Contains(grp, "--") {
   212  		return fmt.Errorf("invalid quota group name: has invalid \"-\" sequences in it")
   213  	}
   214  
   215  	return nil
   216  }