gitee.com/mysnapcore/mysnapd@v0.1.0/store/tooling/auth.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2014-2022 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 tooling 21 22 import ( 23 "bytes" 24 "encoding/base64" 25 "encoding/json" 26 "fmt" 27 "io/ioutil" 28 "net/http" 29 "os" 30 31 "gitee.com/mysnapcore/mysnapd/overlord/auth" 32 "gitee.com/mysnapcore/mysnapd/snapdenv" 33 "gitee.com/mysnapcore/mysnapd/store" 34 "github.com/mvo5/goconfigparser" 35 ) 36 37 type authData struct { 38 // Simple 39 Scheme string 40 Value string 41 42 // U1/SSO 43 Macaroon string 44 Discharges []string 45 } 46 47 type parseAuthFunc func(data []byte, what string) (parsed *authData, likely bool, err error) 48 49 func getAuthorizer() (store.Authorizer, error) { 50 var data []byte 51 var what string 52 parsers := []parseAuthFunc{parseAuthBase64JSON, parseAuthJSON, parseSnapcraftLoginFile} 53 if envStr := os.Getenv("UBUNTU_STORE_AUTH"); envStr != "" { 54 data = []byte(envStr) 55 what = "credentials from UBUNTU_STORE_AUTH" 56 parsers = []parseAuthFunc{parseAuthBase64JSON} 57 } else { 58 authFn := os.Getenv("UBUNTU_STORE_AUTH_DATA_FILENAME") 59 if authFn == "" { 60 return nil, nil 61 } 62 63 var err error 64 data, err = ioutil.ReadFile(authFn) 65 if err != nil { 66 return nil, fmt.Errorf("cannot read auth file %q: %v", authFn, err) 67 } 68 data = bytes.TrimSpace(data) 69 what = fmt.Sprintf("file %q", authFn) 70 } 71 if len(data) == 0 { 72 return nil, fmt.Errorf("invalid auth %s: empty", what) 73 } 74 75 creds, err := parseAuthData(data, what, parsers...) 76 if err != nil { 77 return nil, err 78 } 79 80 if creds.Scheme != "" { 81 return &SimpleCreds{ 82 Scheme: creds.Scheme, 83 Value: creds.Value, 84 }, nil 85 } 86 87 return &UbuntuOneCreds{User: auth.UserState{ 88 StoreMacaroon: creds.Macaroon, 89 StoreDischarges: creds.Discharges, 90 }}, nil 91 } 92 93 func parseAuthData(data []byte, what string, parsers ...parseAuthFunc) (*authData, error) { 94 var firstErr error 95 for _, p := range parsers { 96 parsed, likely, err := p(data, what) 97 if err == nil { 98 return parsed, nil 99 } 100 if likely && firstErr == nil { 101 firstErr = err 102 } 103 } 104 if firstErr == nil { 105 return nil, fmt.Errorf("invalid auth %s: not a recognizable format", what) 106 } 107 return nil, firstErr 108 } 109 110 func parseAuthJSON(data []byte, what string) (*authData, bool, error) { 111 var creds struct { 112 Macaroon string `json:"macaroon"` 113 Discharges []string `json:"discharges"` 114 } 115 err := json.Unmarshal(data, &creds) 116 if err != nil { 117 likely := data[0] == '{' 118 return nil, likely, fmt.Errorf("cannot decode auth %s: %v", what, err) 119 } 120 if creds.Macaroon == "" || len(creds.Discharges) == 0 { 121 return nil, true, fmt.Errorf("invalid auth %s: missing fields", what) 122 } 123 return &authData{ 124 Macaroon: creds.Macaroon, 125 Discharges: creds.Discharges, 126 }, false, nil 127 } 128 129 func snapcraftLoginSection() string { 130 if snapdenv.UseStagingStore() { 131 return "login.staging.ubuntu.com" 132 } 133 return "login.ubuntu.com" 134 } 135 136 // parseSnapcraftLoginFile parses the content of snapcraft <v7 exported 137 // login credentials files. 138 func parseSnapcraftLoginFile(data []byte, what string) (*authData, bool, error) { 139 errPrefix := fmt.Sprintf("invalid snapcraft login %s", what) 140 141 cfg := goconfigparser.New() 142 // XXX this seems to almost always succeed 143 if err := cfg.ReadString(string(data)); err != nil { 144 likely := data[0] == '[' 145 return nil, likely, fmt.Errorf("%s: %v", errPrefix, err) 146 } 147 sec := snapcraftLoginSection() 148 macaroon, err := cfg.Get(sec, "macaroon") 149 if err != nil { 150 return nil, true, fmt.Errorf("%s: %s", errPrefix, err) 151 } 152 unboundDischarge, err := cfg.Get(sec, "unbound_discharge") 153 if err != nil { 154 return nil, true, fmt.Errorf("%s: %v", errPrefix, err) 155 } 156 if macaroon == "" || unboundDischarge == "" { 157 return nil, true, fmt.Errorf("%s: empty fields", errPrefix) 158 } 159 return &authData{ 160 Macaroon: macaroon, 161 Discharges: []string{unboundDischarge}, 162 }, false, nil 163 } 164 165 // parseAuthBase64JSON parses snapcraft v7+ base64-encoded auth credential data. 166 func parseAuthBase64JSON(data []byte, what string) (*authData, bool, error) { 167 jsonData := make([]byte, base64.StdEncoding.DecodedLen(len(data))) 168 dataLen, err := base64.StdEncoding.Decode(jsonData, data) 169 if err != nil { 170 return nil, false, fmt.Errorf("cannot decode base64-encoded auth %s: %v", what, err) 171 } 172 var m map[string]interface{} 173 if err := json.Unmarshal(jsonData[:dataLen], &m); err != nil { 174 return nil, true, fmt.Errorf("cannot unmarshal base64-decoded auth %s: %v", what, err) 175 } 176 r, _ := m["r"].(string) 177 d, _ := m["d"].(string) 178 t, _ := m["t"].(string) 179 switch { 180 case t == "u1-macaroon": 181 v, _ := m["v"].(map[string]interface{}) 182 r, _ = v["r"].(string) 183 d, _ = v["d"].(string) 184 if r == "" || d == "" { 185 break 186 } 187 fallthrough 188 case r != "" && d != "": 189 return &authData{ 190 Macaroon: r, 191 Discharges: []string{d}, 192 }, false, nil 193 case t == "macaroon": 194 v, _ := m["v"].(string) 195 if v != "" { 196 return &authData{ 197 Scheme: "Macaroon", 198 Value: v, 199 }, false, nil 200 } 201 case t == "bearer": 202 v, _ := m["v"].(string) 203 if v != "" { 204 return &authData{ 205 Scheme: "Bearer", 206 Value: v, 207 }, false, nil 208 } 209 } 210 return nil, true, fmt.Errorf("cannot recognize unmarshalled base64-decoded auth %s: no known field combination set", what) 211 } 212 213 // UbuntuOneCreds can authorize requests using the implicitly carried 214 // SSO/U1 user credentials. 215 type UbuntuOneCreds struct { 216 User auth.UserState 217 } 218 219 // expected interfaces 220 var _ store.Authorizer = (*UbuntuOneCreds)(nil) 221 var _ store.RefreshingAuthorizer = (*UbuntuOneCreds)(nil) 222 223 func (c *UbuntuOneCreds) Authorize(r *http.Request, _ store.DeviceAndAuthContext, _ *auth.UserState, _ *store.AuthorizeOptions) error { 224 return store.UserAuthorizer{}.Authorize(r, nil, &c.User, nil) 225 } 226 227 func (c *UbuntuOneCreds) CanAuthorizeForUser(_ *auth.UserState) bool { 228 // UbuntuOneCreds carries a UserState with auth data by construction 229 // so we can authorize using that 230 return true 231 } 232 233 func (c *UbuntuOneCreds) RefreshAuth(_ store.AuthRefreshNeed, _ store.DeviceAndAuthContext, user *auth.UserState, client *http.Client) error { 234 return store.UserAuthorizer{}.RefreshUser(&c.User, c, client) 235 } 236 237 func (c *UbuntuOneCreds) UpdateUserAuth(user *auth.UserState, discharges []string) (*auth.UserState, error) { 238 user.StoreDischarges = discharges 239 return user, nil 240 } 241 242 // SimpleCreds can authorize requests using simply scheme/auth value. 243 type SimpleCreds struct { 244 Scheme string 245 Value string 246 } 247 248 func (c *SimpleCreds) Authorize(r *http.Request, _ store.DeviceAndAuthContext, user *auth.UserState, _ *store.AuthorizeOptions) error { 249 r.Header.Set("Authorization", fmt.Sprintf("%s %s", c.Scheme, c.Value)) 250 return nil 251 } 252 253 func (c *SimpleCreds) CanAuthorizeForUser(_ *auth.UserState) bool { 254 // SimpleCreds can authorize with the implicit auth data it carries 255 // on behalf of the user they were generated for 256 return true 257 }