github.com/apptainer/singularity@v3.1.1+incompatible/internal/pkg/syecl/syecl.go (about) 1 // Copyright (c) 2018, Sylabs Inc. All rights reserved. 2 // This software is licensed under a 3-clause BSD license. Please consult the 3 // LICENSE.md file distributed with the sources of this project regarding your 4 // rights to use or distribute this software. 5 6 // Package syecl implements the loading and management of the container 7 // execution control list feature. This code uses the TOML config file standard 8 // to extract the structured configuration for activating or disabling the list 9 // and for the implementation of the execution groups. 10 package syecl 11 12 import ( 13 "encoding/hex" 14 "fmt" 15 "io/ioutil" 16 "os" 17 "path/filepath" 18 19 toml "github.com/pelletier/go-toml" 20 "github.com/sylabs/singularity/pkg/signing" 21 ) 22 23 // EclConfig describes the structure of an execution control list configuration file 24 type EclConfig struct { 25 Activated bool `toml:"activated"` // toggle the activation of the ECL rules 26 ExecGroups []execgroup `toml:"execgroup"` // Slice of all execution groups 27 } 28 29 // execgroup describes an execution group, the main unit of configuration: 30 // TagName: a descriptive identifier 31 // ListMode: whether the execgroup follows a whitelist, whitestrict or blacklist model 32 // whitelist: one or more KeyFP's present and verified, 33 // whitestrict: all KeyFP's present and verified, 34 // blacklist: none of the KeyFP should be present 35 // DirPath: containers must be stored in this directory path 36 // KeyFPs: list of Key Fingerprints of entities to verify 37 type execgroup struct { 38 TagName string `toml:"tagname"` 39 ListMode string `toml:"mode"` 40 DirPath string `toml:"dirpath"` 41 KeyFPs []string `toml:"keyfp"` 42 } 43 44 // LoadConfig opens an ECL config file and unmarshals it into structures 45 func LoadConfig(confPath string) (ecl EclConfig, err error) { 46 // read in the ECL config file 47 b, err := ioutil.ReadFile(confPath) 48 if err != nil { 49 return 50 } 51 52 // Unmarshal config file 53 err = toml.Unmarshal(b, &ecl) 54 return 55 } 56 57 // PutConfig takes the content of an EclConfig struct and Marshals it to file 58 func PutConfig(ecl EclConfig, confPath string) (err error) { 59 data, err := toml.Marshal(ecl) 60 if err != nil { 61 return 62 } 63 64 return ioutil.WriteFile(confPath, data, 0600) 65 } 66 67 // ValidateConfig makes sure paths from configs are fully resolved and that 68 // values from an execgroup are logically correct. 69 func (ecl *EclConfig) ValidateConfig() (err error) { 70 m := map[string]bool{} 71 72 for _, v := range ecl.ExecGroups { 73 if m[v.DirPath] { 74 return fmt.Errorf("a specific dirpath can only appear in one execgroup: %s", v.DirPath) 75 } 76 m[v.DirPath] = true 77 78 // if we allow containers everywhere, don't test dirpath constraint 79 if v.DirPath != "" { 80 path, err := filepath.EvalSymlinks(v.DirPath) 81 if err != nil { 82 return err 83 } 84 abs, err := filepath.Abs(path) 85 if err != nil { 86 return err 87 } 88 if v.DirPath != abs { 89 return fmt.Errorf("all execgroup dirpath`s should be fully cleaned with symlinks resolved") 90 } 91 } 92 if v.ListMode != "whitelist" && v.ListMode != "whitestrict" && v.ListMode != "blacklist" { 93 return fmt.Errorf("the mode field can only be either: whitelist, whitestrict, blacklist") 94 } 95 for _, k := range v.KeyFPs { 96 decoded, err := hex.DecodeString(k) 97 if err != nil || len(decoded) != 20 { 98 return fmt.Errorf("expecting a 40 chars hex fingerprint string") 99 } 100 } 101 } 102 return 103 } 104 105 // checkWhiteList evaluates authorization by requiring at least 1 entity 106 func checkWhiteList(fp *os.File, egroup *execgroup) (ok bool, err error) { 107 // get all signing entities fingerprints on the primary partition 108 keyfps, err := signing.GetSignEntitiesFp(fp) 109 if err != nil { 110 return 111 } 112 // was the primary partition signed by an authorized entity? 113 for _, v := range egroup.KeyFPs { 114 for _, u := range keyfps { 115 if v == u { 116 ok = true 117 } 118 } 119 } 120 if !ok { 121 return false, fmt.Errorf("%s is not signed by required entities", fp.Name()) 122 } 123 124 return true, nil 125 } 126 127 // checkWhiteStrict evaluates authorization by requiring all entities 128 func checkWhiteStrict(fp *os.File, egroup *execgroup) (ok bool, err error) { 129 // get all signing entities fingerprints on the primary partition 130 keyfps, err := signing.GetSignEntitiesFp(fp) 131 if err != nil { 132 return 133 } 134 135 // was the primary partition signed by all authorized entity? 136 m := map[string]bool{} 137 for _, v := range egroup.KeyFPs { 138 m[v] = false 139 for _, u := range keyfps { 140 if v == u { 141 m[v] = true 142 } 143 } 144 } 145 for _, v := range m { 146 if v != true { 147 return false, fmt.Errorf("%s is not signed by required entities", fp.Name()) 148 } 149 } 150 151 return true, nil 152 } 153 154 // checkBlackList evaluates authorization by requiring all entities to be absent 155 func checkBlackList(fp *os.File, egroup *execgroup) (ok bool, err error) { 156 // get all signing entities fingerprints on the primary partition 157 keyfps, err := signing.GetSignEntitiesFp(fp) 158 if err != nil { 159 return 160 } 161 // was the primary partition signed by an authorized entity? 162 for _, v := range egroup.KeyFPs { 163 for _, u := range keyfps { 164 if v == u { 165 return false, fmt.Errorf("%s is signed by a forbidden entity", fp.Name()) 166 } 167 } 168 } 169 170 return true, nil 171 } 172 173 func shouldRun(ecl *EclConfig, fp *os.File) (ok bool, err error) { 174 var egroup *execgroup 175 176 // look what execgroup a container is part of 177 for _, v := range ecl.ExecGroups { 178 if filepath.Dir(fp.Name()) == v.DirPath { 179 egroup = &v 180 break 181 } 182 } 183 // go back at it and this time look for an empty dirpath execgroup to fallback into 184 if egroup == nil { 185 for _, v := range ecl.ExecGroups { 186 if v.DirPath == "" { 187 egroup = &v 188 break 189 } 190 } 191 } 192 193 if egroup == nil { 194 return false, fmt.Errorf("%s not part of any execgroup", fp.Name()) 195 } 196 197 switch egroup.ListMode { 198 case "whitelist": 199 return checkWhiteList(fp, egroup) 200 case "whitestrict": 201 return checkWhiteStrict(fp, egroup) 202 case "blacklist": 203 return checkBlackList(fp, egroup) 204 } 205 206 return false, fmt.Errorf("ECL config file invalid") 207 } 208 209 // ShouldRun determines if a container should run according to its execgroup rules 210 func (ecl *EclConfig) ShouldRun(cpath string) (ok bool, err error) { 211 // look if ECL rules are activated 212 if ecl.Activated == false { 213 return true, nil 214 } 215 216 fp, err := os.Open(cpath) 217 if err != nil { 218 return false, err 219 } 220 221 return shouldRun(ecl, fp) 222 } 223 224 // ShouldRunFp determines if an already opened container should run according to its execgroup rules 225 func (ecl *EclConfig) ShouldRunFp(fp *os.File) (ok bool, err error) { 226 // look if ECL rules are activated 227 if ecl.Activated == false { 228 return true, nil 229 } 230 231 return shouldRun(ecl, fp) 232 }