github.com/Lephar/snapd@v0.0.0-20210825215435-c7fba9cef4d2/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 }