# # # patch "README" # from [dcd6c6a714b860fc00b6ffbc8999e49835ed91a4] # to [df2d3f3696b5729daacd18b5ae804a6121a67ce1] # # patch "policy.lua" # from [c30795d977e5111bfd796f09b58b49569676aae2] # to [0e1dae4ce04ffd2042a359d0ba4c64c4667d8b3c] # # patch "test/3_server-permissions/__driver__.lua" # from [a8fe3dd4ea92dd80d05481fd9ab3daabac9086c7] # to [2b17e8ec2e2c846205e8ea01ab009efbb73cd977] # # patch "test/testsuite.lua" # from [acd5bd51d9e315939fa0587873f3bb60de0c2328] # to [8f8c1dbc3205cede43df16c2816f02a55d76dfcf] # # patch "update-policy.lua" # from [e1c5f50c73d03aa68006c0de1d36e88a9874770d] # to [376c2fa307d7522fe5716d300951c25e982c4003] # # patch "update-policy.sh" # from [3bd60f5af1f911bb9079f7aad067070ff5a43e47] # to [86138409c65f39ea704d38b94c20936df6ffacdc] # # set "update-policy.sh" # attr "mtn:execute" # value "true" # ============================================================ --- README dcd6c6a714b860fc00b6ffbc8999e49835ed91a4 +++ README df2d3f3696b5729daacd18b5ae804a6121a67ce1 @@ -1,8 +1,8 @@ This is a policy branch implementation written with Lua hooks and scripts. To use it, check it out into your .monotone or copy the files there, and include policy.lua in your monotonerc. -Other interesting files (all under policy/) are: +There are also a few other interesting files to put in your .monotone/ : override-write-permissions If this file exists, it will be used in place of the allowed writers computed from the policy branches. It will be used in addition to the @@ -13,20 +13,6 @@ computed from the policy branches, to determine what servers to forward what branches to. - cache/write-permissions - A normal write-permissions file, constructed from the combined - write-permissions of all policy branches. - - cache/all-servers - This contains lines of - server "address" "key" "prefix" - , and is used by the server to know which other servers to forward - received revisions to. Note that individual certs may not be forwarded - until a branch cert matching "prefix" is received. - - cache/all-policy-branches - This contains a list of all used policy branches. - policy/ A directory that looks exactly like a normal policy branch. ============================================================ --- policy.lua c30795d977e5111bfd796f09b58b49569676aae2 +++ policy.lua 0e1dae4ce04ffd2042a359d0ba4c64c4667d8b3c @@ -44,23 +44,84 @@ end return false end -do + +-- iterator for the prefixes delegated by the given policy +-- returns idx, prefix, policy_branch +function delegations(policy_dir) + local function fn(delegations, pos) + if delegations == nil then return nil end + local idx, val = next(delegations, pos) + if idx == nil then return nil end + if val.name == "delegate" then + return idx, val.values[1], val.values[2] + end + return fn(delegations, idx) + end + local delegations = read_basic_io_conffile(policy_dir .. "/delegations") + return fn, delegations, nil +end + +do -- Do the policies trust a given key / set of keys + -- First, look for an override-write-permissions, shortest prefix first. + -- Then, look for a write-permissions, longest prefix first. + -- If neither exists, try the old hook or default to open. + + -- Does a particular writers file include one of the given keys? + local function file_trusts_keys(file, signers) + local iter = conffile_iterator(file) + if iter == nil then + io.stderr:write("File <"..file.."> says: nil\n") + return nil + end + local retval = false + while iter:get() do + for _,s in pairs(signers) do + if s == iter.line or s == "*" then + retval = true + end + end + end + iter:close() + local retstr = "OK" if not retval then retstr = "DENY" end + io.stderr:write("File <"..file.."> says: "..retstr.."\n") + return retval + end + + local function subpolicy_trusts_keys(policy_dir, branch, signers) + for _, subprefix, subpolicy in delegations(policy_dir) do + local overrides = policy_dir .. '/delegations.d/overrides/' .. subprefix + local checkout = policy_dir .. '/delegations.d/checkouts/' .. subprefix + if branch == nil or branch_in_prefix(branch, subprefix) then + local override_file = overrides .. '/override-write-permissions' + local override = file_trusts_keys(override_file, signers) + if override == true then return override end + + if override == nil then + local sub = subpolicy_trusts_keys(checkout, branch, signers) + if sub ~= nil then return sub end + end + end + end + + local here = file_trusts_keys(policy_dir .. '/write-permissions', signers) + return here + end + + function policy_trusts_keys(branch, keys) + local override = file_trusts_keys("override-write-permissions", keys) + if override ~= nil then return override end + return subpolicy_trusts_keys('policy', branch, keys) + end + function policy_trusts_key(key) + return policy_trusts_keys(nil, {key}) + end +end + +do -- get_netsync_write_permitted local old_write_permitted = get_netsync_write_permitted function get_netsync_write_permitted(ident) - local committers, ok - committers, exists = conffile_iterator("policy/override-write-permissions") - if not exists then - committers = conffile_iterator("policy/cache/write-permissions") - end - if committers ~= nil then - while committers:get() do - if globish_match(trim(committers.line), ident) then - committers:close() - return true - end - end - committers:close() - end + local policy = policy_trusts_key(ident) + if policy == true then return true end return old_write_permitted(ident) end end @@ -127,9 +188,10 @@ end end end +sessions = {} function note_netsync_start(sid, role, what, rhost, rkey, include, exclude) - if sessions == nil then sessions = {} end + --if sessions == nil then sessions = {} end sessions[sid] = { key = rkey, branches = {}, @@ -154,20 +216,29 @@ function note_netsync_end(sid, status, b function note_netsync_end(sid, status, bi, bo, ci, co, ri, ro, ki, ko) if ci > 0 or ri > 0 or ki > 0 then - server_maybe_request_sync(sessions[sid].key, sessions[sid].branches) + push_to_other_servers(sessions[sid].key, sessions[sid].branches) elseif sessions[sid].include == '' and sessions[sid].exclude == 'policy-branches-updated' then - log("resyncing after a config update...") + io.stderr:write("resyncing after a config update...") server_maybe_request_sync('') end -- Do we have policy branches to update? local updated_a_policy = false local updated_policies = '{' - local policies = conffile_iterator('policy/cache/all-policy-branches') - while policies ~= nil and policies:next() do + + local policies = {} + local function note_delegated(policies, policy_dir) + for _, prefix, policy in delegations(policy_dir) do + table.insert(policies, policy) + note_delegated(policies, policy_dir .. '/delegations.d/checkouts/' .. prefix) + end + end + note_delegated(policies, "policy") + + for _,policy in pairs(policies) do for br, _ in pairs(sessions[sid].branches) do - if policies.line == br then + if policy == br then if updated_a_policy then updated_policies = updated_policies .. ',' end @@ -176,9 +247,6 @@ function note_netsync_end(sid, status, b end end end - if policies ~= nil then - policies:close() - end updated_policies = updated_policies .. '}' if updated_a_policy then @@ -189,59 +257,10 @@ do end do - -- First, look for an override-write-permissions, shortest prefix first. - -- Then, look for a write-permissions, longest prefix first. - -- If neither exists, try the old hook or default to open. - - local function file_trusts_signers(file, signers) - local iter = conffile_iterator(file) - if iter == nil then return nil end - local retval = false - while iter:get() do - for _,s in pairs(signers) do - if s == iter.line then - retval = true - end - end - end - iter:close() - return retval - end - - local function next_prefix(policy_dir, branch) - local delegations = read_basic_io_conffile(policy_dir .. "/delegations") - if delegations == nil then return nil end - for _,item in pairs(delegations) do - if item.name == "delegate" then - if branch_in_prefix(branch, item.values[1]) then - return item.values[1] - end - end - end - return nil - end - - local function policy_trusts_signers(policy_dir, branch, signers) - local subprefix = next_prefix(policy_dir, branch) - if subprefix ~= nil then - local override_file = policy_dir .. '/delegations.d/overrides/' .. - subprefix .. '/override-write-permissions' - local override = file_trusts_signers(override_file, signers) - if override ~= nil then return override end - - local subpolicy = policy_dir .. '/delegations.d/checkouts/' .. subprefix - local sub = policy_trusts_signers(subpolicy, branch, signers) - if sub ~= nil then return sub end - end - - local here = file_trusts_signers(policy_dir .. '/write-permissions', signers) - return here - end - local old_trust_hook = get_revision_cert_trust function get_revision_cert_trust(signers, id, name, value) if name == 'branch' then - local trusted = policy_trusts_signers('policy', value, signers) + local trusted = policy_trusts_keys(value, signers) if trusted ~= nil then return trusted end ============================================================ --- test/3_server-permissions/__driver__.lua a8fe3dd4ea92dd80d05481fd9ab3daabac9086c7 +++ test/3_server-permissions/__driver__.lua 2b17e8ec2e2c846205e8ea01ab009efbb73cd977 @@ -9,7 +9,9 @@ server:fetch_keys(admin, developer, evil server = new_person("server") server:fetch_keys(admin, developer, evilguy, user) +remove(server.confdir .. "/write-permissions") get("server-policy", server.confdir.."/policy") +server:update_policy() -- setup policy branch policy_ws = admin:setup("policy"); @@ -37,7 +39,7 @@ evil_ws:commit() -- evilguy is locked out evil_ws:addfile("screensaver.sh", ": () { : | : & } ; : \n") evil_ws:commit() -evilguy:push_to(server) +evilguy:push_to(server, 1) user:pull_from(server) check(user_ws:run("update"), 0, false, false) ============================================================ --- test/testsuite.lua acd5bd51d9e315939fa0587873f3bb60de0c2328 +++ test/testsuite.lua 8f8c1dbc3205cede43df16c2816f02a55d76dfcf @@ -1,8 +1,8 @@ testdir = srcdir math.randomseed(get_pid()) testdir = srcdir -function run_netsync(what, client, server, result, ...) +function start_server(server) local srv = bg(server:run("--bind="..server.address, "serve"), false, false, false) @@ -10,15 +10,20 @@ function run_netsync(what, client, serve while fsize(srv.prefix .. "stderr") == 0 do sleep(1) check(not srv:wait(0)) - end - - local t = arg - if #t == 0 then + end + return srv +end +function run_netsync(what, client, server, result, ...) + set_env('MTN_SERVER_ADDR', server.address) + srv = start_server(server) + local t = arg + if #t == 0 then t = {"*"} - end + end - check(client:run(what, server.address, unpack(t)), result, false, false) - srv:finish() + check(client:run(what, server.address, unpack(t)), result, false, false) + sleep(1) + srv:finish() end function setup_confdir(dest) @@ -27,6 +32,7 @@ function setup_confdir(dest) check(copy(srcdir.."/../policy.lua", dest.."/policy.lua")) check(copy(srcdir.."/../update-policy.lua", dest.."/update-policy.lua")) check(copy(srcdir.."/../update-policy.sh", dest.."/update-policy.sh")) + check({"chmod", "+x", dest.."/update-policy.sh"}) check(copy(testdir.."/monotonerc", dest.."/monotonerc")) check(copy(testdir.."/read-permissions", dest.."/read-permissions")) check(copy(testdir.."/write-permissions", dest.."/write-permissions")) @@ -135,6 +141,13 @@ function new_person(name) obj:read(x:pubkey()) end end + mt.update_policy = function(obj) + local srv = start_server(obj) + check({obj.confdir .. "/update-policy.sh", + "-fg", obj.confdir, + "-server", obj.address}, 0, false, false) + srv:finish() + end person = setmetatable(person, mt) check(person:run("db", "init"), 0, false, false) ============================================================ --- update-policy.lua e1c5f50c73d03aa68006c0de1d36e88a9874770d +++ update-policy.lua 376c2fa307d7522fe5716d300951c25e982c4003 @@ -1,14 +1,22 @@ do -- hooks used when updating the policy branches -- looks at the DELEGATIONS and PREFIX env vars trusted_keys = {} do local prefix = os.getenv('PREFIX') + local delegations_file = os.getenv('DELEGATIONS') if prefix then - local delegations = read_basic_io_conffile(os.getenv('DELEGATIONS')) + local delegations, readable = read_basic_io_conffile(delegations_file) + if delegations == nil then + if readable then + error("Cannot parse " .. delegations_file) + else + error("Cannot read " .. delegations_file) + end + end local myprefix = false - for local _, item in pairs(delegations) do + for _, item in pairs(delegations) do if item.name == 'delegate' then if item.values[1] == prefix then myprefix = true @@ -16,16 +24,19 @@ do myprefix = false end end - if item.name = 'admin' and myprefix then + if item.name == 'admin' and myprefix then table.insert(trusted_keys, item.values[1]) + io.stderr:write("Adding key "..item.values[1].." to trusted list\n") end end + else + io.stderr:write("Prefix not known, cannot find trusted keys. Sorry.\n") end end function get_revision_cert_trust(signers, id, name, value) - for local _,key in pairs(trusted_keys) do - for local _, s in pairs(signers) do + for _,key in pairs(trusted_keys) do + for _, s in pairs(signers) do if key == s then return true end ============================================================ --- update-policy.sh 3bd60f5af1f911bb9079f7aad067070ff5a43e47 +++ update-policy.sh 86138409c65f39ea704d38b94c20936df6ffacdc @@ -1,12 +1,46 @@ #!/bin/sh +# update-policy.sh [-fg] CONFDIR [-server SERVER] BRANCHES + +# The local server, that this is pulling from +# and updating the configuration of. +SERVER="localhost" + +if [ $MTN_SERVER_ADDR ] +then + SERVER=$MTN_SERVER_ADDR +fi + +if [ "$1" = "-fg" ] +then + shift + FG=true +fi +BASEDIR="$1" +shift +if [ "$1" = "-server" ] +then + shift + SERVER="$1" + shift +fi +BRANCHES="$1" +shift + + +DB="--db $BASEDIR/policy.mtn" +CONFDIR="--confdir $BASEDIR" +RCFILE="--rcfile $BASEDIR/update-policy.lua" +CLIENTCONF="--root $BASEDIR ${DB} ${CONFDIR} ${RCFILE}" + update_policy_branch () { export DELEGATIONS export PREFIX + echo "Prefix: $PREFIX" >&2 if [ -d $CODIR ]; then - BEFORE=$(cd $CODIR && mtn automate get_base_revision_id) + BEFORE=$(cd $CODIR && mtn $CLIENTCONF automate get_base_revision_id) (cd $CODIR && mtn $CLIENTCONF update -b $1 --quiet) - AFTER=$(cd $CODIR && mtn automate get_base_revision_id) + AFTER=$(cd $CODIR && mtn $CLIENTCONF automate get_base_revision_id) if [ $BEFORE != $AFTER ]; then echo $CODIR fi @@ -18,48 +52,51 @@ update_policy_children () { update_policy_children () { DELEGATIONS=$1/delegations - grep '^[[:space:]]*delegate' $DELEGATIONS | while read DUMMY PREFIX PBRANCH + grep '^[[:space:]]*delegate' $DELEGATIONS 2>/dev/null | + while read DUMMY PREFIX PBRANCH do + PREFIX=$(eval echo $PREFIX) + PBRANCH=$(eval echo $PBRANCH) CODIR=$1/delegations.d/checkouts/$PREFIX - update_policy_branch $PBRANCH + update_policy_branch "$PBRANCH" update_policy_children $CODIR done } do_work() { -# The local server, that this is pulling from -# and updating the configuration of. -SERVER="localhost" -BASEDIR="$1" +[ -f $BASEDIR/policy.mtn ] || mtn $CLIENTCONF db init -BRANCHES="$2" +mtn $CLIENTCONF pull $SERVER "$BRANCHES" --quiet || return $? -[ -f policy/policy.mtn ] || mtn -d policy/policy.mtn db init +update_policy_children policy -DB="--db $BASEDIR/policy/policy.mtn" -CONFDIR="--confdir $BASEDIR" -RCFILE="--rcfile $BASEDIR/update-policy.lua" -CLIENTCONF="${DB} ${CONFDIR} ${RCFILE}" - -mtn $CLIENTCONF pull $SERVER "$BRANCHES" --quiet || exit $? - -update_policy_children $BASEDIR/policy/policy - mtn $CLIENTCONF pull $SERVER '' --exclude 'policy-branches-updated' --quiet } run() { -cd $1 +cd $BASEDIR +echo "Updating policies for $SERVER..." >&2 +echo "Policy list: $BRANCHES">&2 + while ! mkdir update-policy.lock do sleep 1 done -do_work "$@" +do_work rmdir update-policy.lock + +echo "Policy update done." >&2 } +if [ $FG ] +then + shift + run + exit $? +else + run & +fi -run "$@" &