Skip to main content

Recommended Blueprint

Cordial Scripting Language is a domain-specific language that can be used to initialize all of the policies, users, and credentials on your install. Learn more in the tutorial.

Immediately after setup, there is very little in a Treasury, essentially (see treasury state list --output ccl):

allow access-rule root { action = "any/action", resource = "any/resource", initiate = "users/root" }
invite credential root-invite for root { public_key = "27f4cda174895791f0d2523d6a07320e03eb402a21012171be2f8a4faadfc78c" }
machine user root

This is an insecure state, where anybody can register a client key for this "root" user (with the "well-known" invite code root), which has permissions to do anything with any resource (and without approvals).

Additionally, even the "off-chain worker" machine users (connector, signer-1, and further signers) don't yet exist.

Therefore, some CSL scripts should be run to bootstrap the deployment into a useful state: we call this a blueprint.

To make a Treasury fully functional, at minimal some resources equivalent to the content of our "basic" blueprint is required, which for two nodes can be displayed with treasury blueprint basic 2. It corresponds to the sections base.csl, signer-1.csl, and signer-2.csl in the blueprint displayed in the next section.

This:

  • creates a client key for the root user and enrolls it, consuming the invite credential
  • allows users to register credentials for themselves, send heartbeats
  • it allows root to approve requests by other users (to get them "unstuck") creates roles, users, and invites for the connector and all signer users
  • allows connector and signers to send their expected responses

Blueprint

Run treasury blueprint deployment 2 for an up-to-date version of the following blueprint.

To apply it directly, run treasury blueprint deployment 2 | treasury script.

To modify it before applying, store it in a file blueprint.csl, and then run treasury script -f blueprint.csl

Beyond the basic blueprint, this:

  • creates two "super admin" users admin-1 and admin-2 which, with mutual approval, can do anything (once done, the root user can be retired).
  • sets up a simple policy intended for two "classes" of human users:
    • "admin": intended to manage all resources not directly concerned with transfers
    • "operator": intended to manage all resources directly concerned with transfers
deployment-2.csl

////////
// base.csl
////////

# enroll the root user
root-invite = create invite client key root-invite { code = hex("root") }
root-key = create k256 client key root-key
set sign.with = root-invite
create k256 credential for root { public_key = $root-key.public_key }
delete client key root-invite
set sign.with = root-key

# off-chain worker roles
create role connector { user = "machine", credential = "k256" }
create role signer { user = "machine", credential = "k256" }

# credential registration policy
create allow access-rule create-credential { action = "create", resource = {type = "Credential", variant = ["k256", "web-authn", "web-authn-uv", "session", "ed255", "p256"]}, initiate = "any/user" }
# heartbeat policy
create allow access-rule heartbeats { action = "custom/heartbeat", resource = "User", initiate = "any/user" }
# root approval policy
create allow access-rule root-approve { action = "any/action", resource = "any/resource", approve = "users/root", approvals = 1 }

# off-chain worker policy
create allow access-rule connector-transactions { action = "any/action", resource = "Transaction", initiate = "roles/connector" }
create allow access-rule connector-prices { action = "custom/price", resource = "Asset", initiate = "roles/connector" }
create allow access-rule signer-responses { action = "any/action", resource = ["KeyResponse", "SignatureResponse"], initiate = "roles/signer" }

# connector user and invite
create machine user connector { roles = ["roles/connector"] }
create invite credential for connector { public_key = invite-public(hex("connector")) }

////////
// signer-1.csl
////////

# signer-1 user and invite
create machine user signer-1 { roles = ["roles/signer"] }
create invite credential for signer-1 { public_key = invite-public(hex("signer-1")) }

////////
// signer-2.csl
////////

# signer-2 user and invite
create machine user signer-2 { roles = ["roles/signer"] }
create invite credential for signer-2 { public_key = invite-public(hex("signer-2")) }

////////
// simple.csl
////////

# template roles
create role super-admin { user = "human" }
create role admin { user = "human", credential = ["ed255", "k256", "p256", "web-authn", "web-authn-uv", "session"] }
create role operator { user = "human", credential = ["web-authn", "web-authn-uv", "session"] }
create role programmatic-transfer { user = "machine", credential = ["ed255", "k256", "p256"] }

# template policy
create allow access-rule super-admin { action = "any/action", resource = "any/resource", initiate = "roles/super-admin", approvals = 1 }
create allow access-rule admin { action = "any/action", resource = ["AccessRule", "Credential", "Feature", "Role", "SoftwareUpdate", "User"], initiate = "roles/admin", approvals = 1 }
create allow access-rule operator { action = "any/action", resource = ["Account", "Address", "Asset", "Chain", "Symbol", "TransferRule"], initiate = "roles/operator", approvals = 1 }

create allow access-rule transfers { action = "any/action", resource = "Transfer", initiate = ["roles/operator", "roles/programmatic-transfer"] }
create allow access-rule stakings { action = "any/action", resource = "Staking", initiate = ["roles/operator", "roles/programmatic-transfer"] }
create allow access-rule internal-address { action = "create", resource = { type = "Address", variant = "internal" }, initiate = "roles/operator" }
create allow access-rule delete-allow-rule { action = "delete", resource = { type = "TransferRule", variant = "allow" }, initiate = "roles/operator" }
create allow access-rule create-deny-rule { action = "create", resource = { type = "TransferRule", variant = "deny" }, initiate = "roles/operator" }
create allow access-rule create-require-rule { action = "create", resource = { type = "TransferRule", variant = "require" }, initiate = "roles/operator" }
create allow access-rule red-button { action = "delete", resource = { type = "TransferRule", variant = "allow" }, initiate = "roles/operator" }

////////
// admin-1.csl
////////

# admin-1 user and invite
create human user admin-1 { roles = ["roles/admin", "roles/super-admin"] }
create invite credential for admin-1 { public_key = invite-public(hex("admin-1")) }

////////
// admin-2.csl
////////

# admin-2 user and invite
create human user admin-2 { roles = ["roles/admin", "roles/super-admin"] }
create invite credential for admin-2 { public_key = invite-public(hex("admin-2")) }

Additional peers

The blueprint above was generated for two nodes. To setup more nodes, change the size parameter, for instance treasury blueprint deployment 4 if you have four nodes.

Remove root user

This blueprint uses a root user and root access rule that comes out of the box of a Treasury install.

It must be deleted after you are satisfied with your configuration.

treasury features activate delete_root --sign-with root-key