// Get gets a client from the data store. func Get(clientname string) (*Client, util.Gerror) { var client *Client var err error if config.UsingDB() { client, err = getClientSQL(clientname) if err != nil { var gerr util.Gerror if err != sql.ErrNoRows { gerr = util.Errorf(err.Error()) gerr.SetStatus(http.StatusInternalServerError) } else { gerr = util.Errorf("Client %s not found", clientname) gerr.SetStatus(http.StatusNotFound) } return nil, gerr } } else { ds := datastore.New() c, found := ds.Get("client", clientname) if !found { gerr := util.Errorf("Client %s not found", clientname) gerr.SetStatus(http.StatusNotFound) return nil, gerr } if c != nil { client = c.(*Client) } } return client, nil }
// New creates an empty data bag, and kicks off adding it to the index. func New(name string) (*DataBag, util.Gerror) { var found bool var err util.Gerror if err = validateDataBagName(name, false); err != nil { return nil, err } if config.UsingDB() { var cerr error found, cerr = checkForDataBagSQL(datastore.Dbh, name) if cerr != nil { err = util.Errorf(cerr.Error()) err.SetStatus(http.StatusInternalServerError) return nil, err } } else { ds := datastore.New() _, found = ds.Get("data_bag", name) } if found { err = util.Errorf("Data bag %s already exists", name) err.SetStatus(http.StatusConflict) return nil, err } dbiMap := make(map[string]*DataBagItem) dataBag := &DataBag{ Name: name, DataBagItems: dbiMap, } indexer.CreateNewCollection(name) return dataBag, nil }
// Get a data bag. func Get(dbName string) (*DataBag, util.Gerror) { var dataBag *DataBag var err error if config.UsingDB() { dataBag, err = getDataBagSQL(dbName) if err != nil { var gerr util.Gerror if err == sql.ErrNoRows { gerr = util.Errorf("Cannot load data bag %s", dbName) gerr.SetStatus(http.StatusNotFound) } else { gerr = util.Errorf(err.Error()) gerr.SetStatus(http.StatusInternalServerError) } return nil, gerr } } else { ds := datastore.New() d, found := ds.Get("data_bag", dbName) if !found { err := util.Errorf("Cannot load data bag %s", dbName) err.SetStatus(http.StatusNotFound) return nil, err } if d != nil { dataBag = d.(*DataBag) for _, v := range dataBag.DataBagItems { z := datastore.WalkMapForNil(v.RawData) v.RawData = z.(map[string]interface{}) } } } return dataBag, nil }
// New creates a new report. func New(runID string, nodeName string) (*Report, util.Gerror) { var found bool if config.UsingDB() { var err error found, err = checkForReportSQL(datastore.Dbh, runID) if err != nil { gerr := util.CastErr(err) gerr.SetStatus(http.StatusInternalServerError) return nil, gerr } } else { ds := datastore.New() _, found = ds.Get("report", runID) } if found { err := util.Errorf("Report already exists") err.SetStatus(http.StatusConflict) return nil, err } if u := uuid.Parse(runID); u == nil { err := util.Errorf("run id was not a valid uuid") err.SetStatus(http.StatusBadRequest) return nil, err } report := &Report{ RunID: runID, NodeName: nodeName, Status: "started", } return report, nil }
func (cbv *CookbookVersion) deleteCookbookVersionSQL() util.Gerror { tx, err := datastore.Dbh.Begin() if err != nil { gerr := util.Errorf(err.Error()) gerr.SetStatus(http.StatusInternalServerError) return gerr } if config.Config.UseMySQL { _, err = tx.Exec("DELETE FROM cookbook_versions WHERE id = ?", cbv.id) } else if config.Config.UsePostgreSQL { _, err = tx.Exec("DELETE FROM goiardi.cookbook_versions WHERE id = $1", cbv.id) } if err != nil { terr := tx.Rollback() if terr != nil { err = fmt.Errorf("deleting cookbook %s version %s had an error '%s', and then rolling back the transaction gave another error '%s'", cbv.CookbookName, cbv.Version, err.Error(), terr.Error()) } gerr := util.Errorf(err.Error()) gerr.SetStatus(http.StatusInternalServerError) return gerr } tx.Commit() return nil }
func assembleSignedHeader(r *http.Request) (string, util.Gerror) { sHeadStore := make(map[int]string) authHeader := regexp.MustCompile(`(?i)^X-Ops-Authorization-(\d+)`) for k := range r.Header { if c := authHeader.FindStringSubmatch(k); c != nil { /* Have to put it into a map first, then sort, in case * the headers don't come out in the right order */ // skipping this error because we shouldn't even be // able to get here with something that won't be an // integer. Famous last words, I'm sure. i, _ := strconv.Atoi(c[1]) sHeadStore[i] = r.Header.Get(k) } } if len(sHeadStore) == 0 { gerr := util.Errorf("No authentication headers found!") gerr.SetStatus(http.StatusUnauthorized) return "", gerr } sH := make([]string, len(sHeadStore)) sHlimit := len(sH) for k, v := range sHeadStore { if k > sHlimit { gerr := util.Errorf("malformed authentication headers") gerr.SetStatus(http.StatusUnauthorized) return "", gerr } sH[k-1] = v } signedHeaders := strings.Join(sH, "") return signedHeaders, nil }
func extractVerNums(cbVersion string) (maj, min, patch int64, err util.Gerror) { if _, err = util.ValidateAsVersion(cbVersion); err != nil { return 0, 0, 0, err } nums := strings.Split(cbVersion, ".") if len(nums) < 2 && len(nums) > 3 { err = util.Errorf("incorrect number of numbers in version string '%s': %d", cbVersion, len(nums)) return 0, 0, 0, err } var vt int64 var nerr error vt, nerr = strconv.ParseInt(nums[0], 0, 64) if nerr != nil { err = util.Errorf(nerr.Error()) return 0, 0, 0, err } maj = vt vt, nerr = strconv.ParseInt(nums[1], 0, 64) if nerr != nil { err = util.Errorf(nerr.Error()) return 0, 0, 0, err } min = vt if len(nums) == 3 { vt, nerr = strconv.ParseInt(nums[2], 0, 64) if nerr != nil { err = util.Errorf(nerr.Error()) return 0, 0, 0, err } patch = vt } else { patch = 0 } return maj, min, patch, nil }
func (u *User) renameMySQL(newName string) util.Gerror { tx, err := datastore.Dbh.Begin() if err != nil { gerr := util.Errorf(err.Error()) return gerr } if err = chkForClient(tx, newName); err != nil { tx.Rollback() gerr := util.Errorf(err.Error()) return gerr } found, err := checkForUserSQL(datastore.Dbh, newName) if found || err != nil { tx.Rollback() if found && err == nil { gerr := util.Errorf("User %s already exists, cannot rename %s", newName, u.Username) gerr.SetStatus(http.StatusConflict) return gerr } gerr := util.Errorf(err.Error()) gerr.SetStatus(http.StatusInternalServerError) return gerr } _, err = tx.Exec("UPDATE users SET name = ? WHERE name = ?", newName, u.Username) if err != nil { tx.Rollback() gerr := util.Errorf(err.Error()) gerr.SetStatus(http.StatusInternalServerError) return gerr } tx.Commit() return nil }
// New creates a new cookbook. func New(name string) (*Cookbook, util.Gerror) { var found bool if !util.ValidateName(name) { err := util.Errorf("Invalid cookbook name '%s' using regex: 'Malformed cookbook name. Must only contain A-Z, a-z, 0-9, _ or -'.", name) return nil, err } if config.UsingDB() { var cerr error found, cerr = checkForCookbookSQL(datastore.Dbh, name) if cerr != nil { err := util.CastErr(cerr) err.SetStatus(http.StatusInternalServerError) return nil, err } } else { ds := datastore.New() _, found = ds.Get("cookbook", name) } if found { err := util.Errorf("Cookbook %s already exists", name) err.SetStatus(http.StatusConflict) } cookbook := &Cookbook{ Name: name, Versions: make(map[string]*CookbookVersion), } return cookbook, nil }
func (cbv *CookbookVersion) updateCookbookVersionMySQL(defb, libb, attb, recb, prob, resb, temb, roob, filb, metb []byte, maj, min, patch int64) util.Gerror { tx, err := datastore.Dbh.Begin() if err != nil { gerr := util.Errorf(err.Error()) gerr.SetStatus(http.StatusInternalServerError) return gerr } res, err := tx.Exec("INSERT INTO cookbook_versions (cookbook_id, major_ver, minor_ver, patch_ver, frozen, metadata, definitions, libraries, attributes, recipes, providers, resources, templates, root_files, files, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW()) ON DUPLICATE KEY UPDATE id=LAST_INSERT_ID(id), frozen = ?, metadata = ?, definitions = ?, libraries = ?, attributes = ?, recipes = ?, providers = ?, resources = ?, templates = ?, root_files = ?, files = ?, updated_at = NOW()", cbv.cookbookID, maj, min, patch, cbv.IsFrozen, metb, defb, libb, attb, recb, prob, resb, temb, roob, filb, cbv.IsFrozen, metb, defb, libb, attb, recb, prob, resb, temb, roob, filb) if err != nil { tx.Rollback() gerr := util.CastErr(err) gerr.SetStatus(http.StatusInternalServerError) return gerr } cID, err := res.LastInsertId() if err != nil { tx.Rollback() gerr := util.Errorf(err.Error()) gerr.SetStatus(http.StatusInternalServerError) return gerr } cbv.id = int32(cID) tx.Commit() return nil }
// Get a user. func Get(name string) (*User, util.Gerror) { var user *User if config.UsingDB() { var err error user, err = getUserSQL(name) if err != nil { var gerr util.Gerror if err != sql.ErrNoRows { gerr = util.Errorf(err.Error()) gerr.SetStatus(http.StatusInternalServerError) } else { gerr = util.Errorf("Client %s not found", name) gerr.SetStatus(http.StatusNotFound) } return nil, gerr } } else { ds := datastore.New() u, found := ds.Get("user", name) if !found { err := util.Errorf("User %s not found", name) return nil, err } if u != nil { user = u.(*User) } } return user, nil }
// New creates a new environment, returning an error if the environment already // exists or you try to create an environment named "_default". func New(name string) (*ChefEnvironment, util.Gerror) { if !util.ValidateEnvName(name) { err := util.Errorf("Field 'name' invalid") err.SetStatus(http.StatusBadRequest) return nil, err } var found bool if config.UsingDB() { var eerr error found, eerr = checkForEnvironmentSQL(datastore.Dbh, name) if eerr != nil { err := util.CastErr(eerr) err.SetStatus(http.StatusInternalServerError) return nil, err } } else { ds := datastore.New() _, found = ds.Get("env", name) } if found || name == "_default" { err := util.Errorf("Environment already exists") return nil, err } env := &ChefEnvironment{ Name: name, ChefType: "environment", JSONClass: "Chef::Environment", Default: map[string]interface{}{}, Override: map[string]interface{}{}, CookbookVersions: map[string]string{}, } return env, nil }
// UpdateFromJSON updates a user from a JSON object, carrying out a bunch of // validations inside. func (u *User) UpdateFromJSON(jsonUser map[string]interface{}) util.Gerror { userName, nerr := util.ValidateAsString(jsonUser["name"]) if nerr != nil { return nerr } if u.Username != userName { err := util.Errorf("User name %s and %s from JSON do not match", u.Username, userName) return err } /* Validations. */ /* Invalid top level elements */ validElements := []string{"username", "name", "org_name", "public_key", "private_key", "admin", "password", "email", "salt"} ValidElem: for k := range jsonUser { for _, i := range validElements { if k == i { continue ValidElem } } err := util.Errorf("Invalid key %s in request body", k) return err } var verr util.Gerror // Check the password first. If it's bad, bail before touching anything // else. if passwd, ok := jsonUser["password"]; ok { passwd, verr = util.ValidateAsString(passwd) if verr != nil { return verr } if passwd != "" { verr = u.SetPasswd(passwd.(string)) if verr != nil { return verr } } } if adminVal, ok := jsonUser["admin"]; ok { var ab bool if ab, verr = util.ValidateAsBool(adminVal); verr != nil { // NOTE: may need to tweak this error message depending // if this is a user or a client verr = util.Errorf("Field 'admin' invalid") return verr } else if u.Admin && !ab { if u.isLastAdmin() { verr = util.Errorf("Cannot remove admin status from the last admin") verr.SetStatus(http.StatusForbidden) return verr } } u.Admin = ab } return nil }
// CheckPasswd checks the provided password to see if it matches the stored // password hash. func (u *User) CheckPasswd(password string) util.Gerror { h, perr := chefcrypto.HashPasswd(password, u.salt) if perr != nil { err := util.Errorf(perr.Error()) return err } if u.passwd != h { err := util.Errorf("password did not match") return err } return nil }
// SetPasswd validates and sets the user's password. Will not set a password for // a client. func (u *User) SetPasswd(password string) util.Gerror { if len(password) < 6 { err := util.Errorf("Password must have at least 6 characters") return err } /* If those validations pass, set the password */ var perr error u.passwd, perr = chefcrypto.HashPasswd(password, u.salt) if perr != nil { err := util.Errorf(perr.Error()) return err } return nil }
// DeleteVersion deletes a particular version of a cookbook. func (c *Cookbook) DeleteVersion(cbVersion string) util.Gerror { /* Check for existence */ cbv, _ := c.GetVersion(cbVersion) if cbv == nil { err := util.Errorf("Version %s of cookbook %s does not exist to be deleted.", cbVersion, c.Name) err.SetStatus(http.StatusNotFound) return err } fhashes := cbv.fileHashes() if config.UsingDB() { err := cbv.deleteCookbookVersionSQL() if err != nil { return nil } } c.numVersions = nil delete(c.Versions, cbVersion) c.Save() c.deleteHashes(fhashes) return nil }
func checkAuthHeaders(publicKey string, r *http.Request, headToCheck, signedHeaders string) util.Gerror { decHead, berr := chefcrypto.HeaderDecrypt(publicKey, signedHeaders) if berr != nil { gerr := util.Errorf(berr.Error()) gerr.SetStatus(http.StatusUnauthorized) return gerr } if string(decHead) != headToCheck { gerr := util.Errorf("failed to verify authorization") gerr.SetStatus(http.StatusUnauthorized) return gerr } return nil }
// Get a report. func Get(runID string) (*Report, util.Gerror) { var report *Report var found bool if config.UsingDB() { var err error report, err = getReportSQL(runID) if err != nil { if err == sql.ErrNoRows { found = false } else { gerr := util.CastErr(err) gerr.SetStatus(http.StatusInternalServerError) return nil, gerr } } else { found = true } } else { ds := datastore.New() var r interface{} r, found = ds.Get("report", runID) if r != nil { report = r.(*Report) } } if !found { err := util.Errorf("Report %s not found", runID) err.SetStatus(http.StatusNotFound) return nil, err } return report, nil }
// NewVersion creates a new version of the cookbook. func (c *Cookbook) NewVersion(cbVersion string, cbvData map[string]interface{}) (*CookbookVersion, util.Gerror) { if _, err := c.GetVersion(cbVersion); err == nil { err := util.Errorf("Version %s of cookbook %s already exists, and shouldn't be created like this. Use UpdateVersion instead.", cbVersion, c.Name) err.SetStatus(http.StatusConflict) return nil, err } cbv := &CookbookVersion{ CookbookName: c.Name, Version: cbVersion, Name: fmt.Sprintf("%s-%s", c.Name, cbVersion), ChefType: "cookbook_version", JSONClass: "Chef::CookbookVersion", IsFrozen: false, cookbookID: c.id, // should be ok even with in-mem } err := cbv.UpdateVersion(cbvData, "") if err != nil { return nil, err } /* And, dur, add it to the versions */ c.Versions[cbVersion] = cbv c.numVersions = nil c.UpdateLatestVersion() c.Save() return cbv, nil }
// RecipeList provides a list of recipes in this cookbook version. func (cbv *CookbookVersion) RecipeList() ([]string, util.Gerror) { recipeMeta := cbv.Recipes recipes := make([]string, len(recipeMeta)) ci := 0 /* Cobble the recipes together from the Recipes field */ for _, r := range recipeMeta { rm := regexp.MustCompile(`(.*?)\.rb`) rfind := rm.FindStringSubmatch(r["name"].(string)) if rfind == nil { /* unlikely */ err := util.Errorf("No recipe name found") return nil, err } rbase := rfind[1] var rname string if rbase == "default" { rname = cbv.CookbookName } else { rname = fmt.Sprintf("%s::%s", cbv.CookbookName, rbase) } recipes[ci] = rname ci++ } return recipes, nil }
// Get a node. func Get(nodeName string) (*Node, util.Gerror) { var node *Node var found bool if config.UsingDB() { var err error node, err = getSQL(nodeName) if err != nil { if err == sql.ErrNoRows { found = false } else { return nil, util.CastErr(err) } } else { found = true } } else { ds := datastore.New() var n interface{} n, found = ds.Get("node", nodeName) if n != nil { node = n.(*Node) } } if !found { err := util.Errorf("node '%s' not found", nodeName) err.SetStatus(http.StatusNotFound) return nil, err } return node, nil }
func validateClientName(name string) util.Gerror { if !util.ValidateName(name) { err := util.Errorf("Invalid client name '%s' using regex: 'Malformed client name. Must be A-Z, a-z, 0-9, _, -, or .'.", name) return err } return nil }
func validateUserName(name string) util.Gerror { if !util.ValidateUserName(name) { err := util.Errorf("Field 'name' invalid") return err } return nil }
func chkInMemUser(name string) util.Gerror { var err util.Gerror ds := datastore.New() if _, found := ds.Get("user", name); found { err = util.Errorf("a user named %s was found that would conflict with this client", name) err.SetStatus(http.StatusConflict) } return err }
// CheckHeader checks the signed headers sent by the client against the expected // result assembled from the request headers to verify their authorization. func CheckHeader(userID string, r *http.Request) util.Gerror { user, err := actor.GetReqUser(userID) if err != nil { gerr := util.Errorf("Failed to authenticate as '%s'. Ensure that your node_name and client key are correct.", userID) gerr.SetStatus(http.StatusUnauthorized) return gerr } return AuthenticateHeader(user.PublicKey(), config.Config.TimeSlewDur, r) }
// GetVersion gets a particular version of the cookbook. func (c *Cookbook) GetVersion(cbVersion string) (*CookbookVersion, util.Gerror) { if cbVersion == "_latest" { return c.LatestVersion(), nil } var cbv *CookbookVersion var found bool if config.UsingDB() { // Ridiculously cacheable, but let's get it working first. This // applies all over the place w/ the SQL bits. if cbv, found = c.Versions[cbVersion]; !found { var err error cbv, err = c.getCookbookVersionSQL(cbVersion) if err != nil { if err == sql.ErrNoRows { found = false } else { gerr := util.Errorf(err.Error()) gerr.SetStatus(http.StatusInternalServerError) return nil, gerr } } else { found = true c.Versions[cbVersion] = cbv } } } else { cbv, found = c.Versions[cbVersion] if cbv != nil { datastore.ChkNilArray(cbv) if cbv.Recipes == nil { cbv.Recipes = make([]map[string]interface{}, 0) } } } if !found { err := util.Errorf("Cannot find a cookbook named %s with version %s", c.Name, cbVersion) err.SetStatus(http.StatusNotFound) return nil, err } return cbv, nil }
func checkTimeStamp(timestamp string, slew time.Duration) (bool, util.Gerror) { timeNow := time.Now().UTC() timeHeader, terr := time.Parse(time.RFC3339, timestamp) if terr != nil { err := util.Errorf(terr.Error()) err.SetStatus(http.StatusUnauthorized) return false, err } tdiff := timeNow.Sub(timeHeader) // no easy integer based abs function if tdiff < 0 { tdiff = -tdiff } if tdiff > slew { err := util.Errorf("Authentication failed. Please check your system's clock.") err.SetStatus(http.StatusUnauthorized) return false, err } return true, nil }
func (u *User) renamePostgreSQL(newName string) util.Gerror { tx, err := datastore.Dbh.Begin() if err != nil { gerr := util.Errorf(err.Error()) return gerr } _, err = tx.Exec("SELECT goiardi.rename_user($1, $2, $3)", u.Username, newName, defaultOrgID) if err != nil { tx.Rollback() gerr := util.Errorf(err.Error()) if strings.HasPrefix(err.Error(), "a client with") || strings.Contains(err.Error(), "already exists, cannot rename") { gerr.SetStatus(http.StatusConflict) } else { gerr.SetStatus(http.StatusInternalServerError) } return gerr } tx.Commit() return nil }
// CheckPermEdit checks to see if the user is trying to edit admin and // validator attributes, and if it has permissions to do so. func (u *User) CheckPermEdit(userData map[string]interface{}, perm string) util.Gerror { gerr := util.Errorf("You are not allowed to take this action.") gerr.SetStatus(http.StatusForbidden) if av, ok := userData[perm]; ok { if a, _ := util.ValidateAsBool(av); a { return gerr } } return nil }
// New makes a new node. func New(name string) (*Node, util.Gerror) { /* check for an existing node with this name */ if !util.ValidateDBagName(name) { err := util.Errorf("Field 'name' invalid") return nil, err } var found bool if config.UsingDB() { // will need redone if orgs ever get implemented var err error found, err = checkForNodeSQL(datastore.Dbh, name) if err != nil { gerr := util.Errorf(err.Error()) gerr.SetStatus(http.StatusInternalServerError) return nil, gerr } } else { ds := datastore.New() _, found = ds.Get("node", name) } if found { err := util.Errorf("Node %s already exists", name) err.SetStatus(http.StatusConflict) return nil, err } /* No node, we make a new one */ node := &Node{ Name: name, ChefEnvironment: "_default", ChefType: "node", JSONClass: "Chef::Node", RunList: []string{}, Automatic: map[string]interface{}{}, Normal: map[string]interface{}{}, Default: map[string]interface{}{}, Override: map[string]interface{}{}, } return node, nil }