In the fixie directory, run bundle install --binstubs
On chef server installs, fixie can read configuration information out of /etc/opscode, specifically the chef-server-running.json file.
Otherwise, you will need a fixie.conf file with the appropriate URIs and secrets for accessing postgres and bifrost.
The fixie.conf.example file contains examples from a instance of private chef.
Attribute | Description | Example |
---|---|---|
authz_uri | The URI for the bifrost/authz service | http://localhost:9463 |
superuser_id | The authz superuser id | fa84f0f5524a06baaa10b0f988ff2d8f |
sql_database | A Ruby Sequel compatible database URI with user and password | postgres://opscode_chef:3b2bb8affc0b87233130f820443aecca2061fadc6c0df16828233e433877ca1c552d1b9d40d3971f0e8c21fc4b7ff9471d91@localhost/opscode_chef |
Start fixie with
bin/fixie fixie.conf
The config file is optional, leaving it out will attempt to read /etc/opscode for configuration.
There are two core class hierarchies in fixie, one corresponding to a table (e.g. Orgs), and another corresponding to a row in the table (e.g. Org). The naming convention is that the plural refers to the table and the singular refers to the row. These are tightly coupled, and generally if one exists the other will too.
The table class (e.g. Orgs) provides access to the raw Sequel objects. There are several constants in the REPL containing pre-initialized instances of the commonly accessed table classes, including ORGS, USERS, ASSOCIATIONS, and INVITES.
Each of these have accessors to search by various columns. These use the naming pattern #by_XXXX, where XXX is the column name in the database. For example ORGS.by_name('ponyville') searches for the org named 'ponyville'. The return value from this accessor is a new instance of the object that can be further refined. In many cases aliases have been added to hide naming quirks. For example groups use the 'groupname' column, but they can also be accessed using the #by_name function.
The REPL supports method completion, so the list of supported filters can be found by typing ORGS.by_
and hitting the tab key.
To get a list of all objects selected use the #all method. To list the names of the objects use the #list method:
fixie:0 > ORGS.list
["acme", "ponyville", "wonderbolts"]
fixie:0 > ORGS.all
[#<Fixie::Sql::Org:0x00000003886fa0 @data=#< @values={:id=>"ca0542c21119786fd4d2ddeb5c920ecf", :authz_id=>"baefe78d2fdab7d31fce7f4bdd6feda8", :name=>"ponyville", :full_name=>"ponyville", :assigned_at=>2015-02-05 03:06:33 UTC, :last_updated_by=>"9f6f823739fe6417b1c247ca0d2afdfc", :created_at=>2015-02-05 03:06:33 UTC, :updated_at=>2015-02-05 03:06:33 UTC}>>, #<Fixie::Sql::Org:0x00000003886f50 @data=#< @values={:id=>"2742f6f01ae95aa5998fd7ad94e0d383", :authz_id=>"52064f4a67a2b6c0243051e9f855699a", :name=>"wonderbolts", :full_name=>"wonderbolts", :assigned_at=>2015-02-05 03:07:05 UTC, :last_updated_by=>"9f6f823739fe6417b1c247ca0d2afdfc", :created_at=>2015-02-05 03:07:05 UTC, :updated_at=>2015-02-05 03:07:05 UTC}>>, #<Fixie::Sql::Org:0x00000003886f28 @data=#< @values={:id=>"0434803f600f1688707081921cf92721", :authz_id=>"b9a9dee90b6c2ab31cf4350aeba59460", :name=>"acme", :full_name=>"acme", :assigned_at=>2015-02-05 03:07:32 UTC, :last_updated_by=>"9f6f823739fe6417b1c247ca0d2afdfc", :created_at=>2015-02-05 03:07:32 UTC, :updated_at=>2015-02-05 03:07:32 UTC}>>]
The #all and #list functions have protection against accidentally grabbing the entire table; if too many
results are included (currently 10), it returns :too_many_results
instead. Providing a parameter to the #all
and #list function adjusts the limit.
Any object that has a well defined 'name' has the index ('[]') accessor provided; this takes a string and returns a single exact match for it if it exists.
- USERS
This allows access to the users table in sql.
To get the user named 'rainbowdash'
> u = USERS['rainbowdash']
#<Fixie::Sql::User:0x00000002afea18 @data=#< @values={:id=>"0000000000004d3eac4cc85b2bdddd0f", :authz_id=>"070dc1cd727a6b71e48d5e16f8d7b137", :username=>"rainbowdash", :email=>"[email protected]", :pubkey_version=>0, :public_key=>"-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAr0ZeWeIuU+rO2m1Pe8Nk\n7kzkqmk+CbaP8CVc0OlPZZITgoW2NEseCg1N3FVrCGIIY8vhDkyPST7ZKNva/hOo\nltC8inN695wRchQ1EDpVityL7EIuu7haXBib2WA2HQezlRWKMdrMGGRq0bMa3lD4\nV/YfEXSBtkE8W7QaanbtpgipWC1VGorj0MLR+++JYGd9kqGp49DiC7FH+DChE6pj\nRD9d25/chclD+svZy7RW0s2Q0/H/qRjhOdHoBGljJohVF64CsfqhDCr02zytbKDy\n6sOFjFneSqDZhlx81uVtQ0l+H+0bx77zbwLtp/WjpUFjw/yA8V92/WCjvwMTUaRN\nxQIDAQAB\n-----END PUBLIC KEY-----\n\n", :serialized_object=>"{\"display_name\":\"rainbowdash pony\",\"first_name\":\"rainbowdash\",\"last_name\":\"pony\",\"middle_name\":\"\"}", :last_updated_by=>"c8bd48b83f61031c29ab4ff5168fccd2", :created_at=>2014-10-31 16:59:37 UTC, :updated_at=>2014-10-31 16:59:37 UTC, :external_authentication_uid=>nil, :recovery_authentication_enabled=>false, :admin=>false, :hashed_password=>"$2a$12$FZMrxfVxWLpj8xPiBXG6SO2YxqGMp3zAj7I4w7cr50y1VbCbgIrUe", :salt=>"$2a$12$FZMrxfVxWLpj8xPiBXG6SO", :hash_type=>"bcrypt"}>>
To see just the name of that user:
> u.name
"rainbowdash"
To find all users with ponyville in their name:
> USERS.by_email(/ponyville/)
#<Fixie::Sql::Users:0x00000003514200 @inner=#<Sequel::Postgres::Dataset: "SELECT * FROM \"users\" WHERE (\"email\" ~ 'ponyville')">>
Note this returns a users table object which can be refined further. To process the list of users selected, use the #all method:
> USERS.by_email(/ponyville/).all.map {|x| x.name }
["rainbowdash", "fluttershy", "applejack", "pinkiepie", "twilightsparkle", "rarity"]
To further refine the set of objects further selectors can be applied:
fixie:0 > USERS.by_email(/ponyville/).by_username(/apple/).all.first.name
"applejack"
The #all method has a hidden limit (which may be changed in future versions). It has takes a paramenter max_count (defaults to 10). If it will return more than max_count elements it returns :too_many_results instead; this is to prevent filling the screen or slammming the database by accident.
fixie:0 > USERS.all
:too_many_results
- ORGS
ORGS work very similarly to USERS, but the org object return also adds accessors for org scoped objects such as nodes, roles, groups, containers, etc.
fixie:0 > ORGS['ponyville'].full_name
"ponyville"
fixie:0 > ORGS['ponyville'].groups.all.map {|x| x.name}
["admins", "billing-admins", "clients", "0000000000004d3eac4cc85b2bdddd0f", "000000000000506ccf528a2844e81838", "000000000000808da6731453e12eb2bb", "000000000000dfccddd011ce219caaf0", "00000000000036639a19f27527b29a3e", "000000000000224a1c0e395b112c1d20", "users"]
- ASSOCS
- INVITES
- GLOBAL_GROUPS
- GLOBAL_CONTAINERS
The objects returned by the above selectors have accessors to allow editing of acl and group membership.
ACLs can be viewed for any object
fixie:0 > ORGS['ponyville'].clients['ponyville-validator'].acl
{"create"=>{"actors"=>[[:global, "pivotal"]], "groups"=>[["ponyville", "admins"]]}, "read"=>{"actors"=>[[:global, "pivotal"]], "groups"=>[["ponyville", "admins"], ["ponyville", "users"]]}, "update"=>{"actors"=>[[:global, "pivotal"]], "groups"=>[["ponyville", "admins"]]}, "delete"=>{"actors"=>[[:global, "pivotal"]], "groups"=>[["ponyville", "admins"], ["ponyville", "users"]]}, "grant"=>{"actors"=>[[:global, "pivotal"]], "groups"=>[["ponyville", "admins"]]}}
Individual ACEs can be viewed
fixie:0 > ORGS['ponyville'].clients['ponyville-validator'].ace(:read)
{"actors"=>[[:global, "pivotal"], [:global, "fluttershy"]], "groups"=>[["ponyville", "admins"], ["ponyville", "users"]]}
Users and groups can be added to an ACE. The APIs are 'magic' in the sense that they can take an object and figure out if it is an actor or group and add it appropriately.
fixie:0 > ORGS['ponyville'].clients['ponyville-validator'].ace_add(:read, USERS['fluttershy'])
{}
fixie:0 > ORGS['ponyville'].clients['ponyville-validator'].ace_add(:read, GLOBAL_GROUPS['ponyville_global_admins'])
{}
fixie:0 > ORGS['ponyville'].clients['ponyville-validator'].ace(:read)
{"actors"=>[[:global, "pivotal"], [:global, "fluttershy"]], "groups"=>[["ponyville", "admins"], ["ponyville", "users"], ["unknown-00000000000000000000000000000000", "ponyville_global_admins"]]}
And removed:
fixie:0 > ORGS['ponyville'].clients['ponyville-validator'].ace_delete(:read, GLOBAL_GROUPS['ponyville_global_admins'])
{}
fixie:0 > ORGS['ponyville'].clients['ponyville-validator'].ace(:read)
{"actors"=>[[:global, "pivotal"], [:global, "fluttershy"]], "groups"=>[["ponyville", "admins"], ["ponyville", "users"]]}
fixie:0 > ORGS['ponyville'].clients['ponyville-validator'].ace_delete(:read, USERS['fluttershy'])
{}
fixie:0 > ORGS['ponyville'].clients['ponyville-validator'].ace(:read)
{"actors"=>[[:global, "pivotal"]], "groups"=>[["ponyville", "admins"], ["ponyville", "users"]]}
The ace_add and ace_delete functions take the symbols :create, :read, :update, :delete and :grant in the action field. The user can also provide an array of those symbols for bulk operations, or specify :all to edit all of the actions.
fixie:0 > ORGS['ponyville'].clients['ponyville-validator'].ace_add(:all, USERS['pivotal'])
fixie:0 > ORGS['ponyville'].clients['ponyville-validator'].ace_add([:read,:update], USERS['fluttershy'])
Sometimes it is useful to copy permisons from one object to another. For example, if an object has had a catastrophic permission edit, you'd like to at least restore the permissions it would have inherited from the container. This command adds permissions from another object, but does not remove any permisions already there.
fixie:0 > clients_container = ORGS['ponyville'].containers['clients']
fixie:0 > ORGS['ponyville'].clients['ponyville-validator'].acl_add_from_object(clients_container)
There are also _raw versions of these functions that work on the raw authz ids
fixie:0 > ORGS['ponyville'].clients['ponyville-validator'].acl_raw
{"create"=>{"actors"=>["c8bd48b83f61031c29ab4ff5168fccd2"], "groups"=>["ca0a8cabaafaae658edd298ddd4266cd"]}, "read"=>{"actors"=>["c8bd48b83f61031c29ab4ff5168fccd2"], "groups"=>["ca0a8cabaafaae658edd298ddd4266cd", "690063dd87f110eabfa5ba387b8e280f"]}, "update"=>{"actors"=>["c8bd48b83f61031c29ab4ff5168fccd2"], "groups"=>["ca0a8cabaafaae658edd298ddd4266cd"]}, "delete"=>{"actors"=>["c8bd48b83f61031c29ab4ff5168fccd2"], "groups"=>["ca0a8cabaafaae658edd298ddd4266cd", "690063dd87f110eabfa5ba387b8e280f"]}, "grant"=>{"actors"=>["c8bd48b83f61031c29ab4ff5168fccd2"], "groups"=>["ca0a8cabaafaae658edd298ddd4266cd"]}}
Groups can be viewed:
fixie:0 > ORGS['ponyville'].groups['admins'].group
{"actors"=>[[:global, "pivotal"]], "groups"=>[]}
Members can be added/removed from groups:
fixie:0 > ORGS['ponyville'].groups['admins'].group_add(USERS['fluttershy'])
{}
fixie:0 > ORGS['ponyville'].groups['admins'].group_delete(USERS['fluttershy'])
{}
Again there are raw functions that take raw authz ids.
fixie:0 > ORGS['ponyville'].groups['admins'].group_raw
{"actors"=>["c8bd48b83f61031c29ab4ff5168fccd2"], "groups"=>[]}