Wallet::Schema(3pm)

πŸ“¦ version 1.7, in debian trixie, ubuntu resolute, ubuntu stonking

πŸ’– Wallet::Schema πŸ’–

okay bestie, this is the perl manual page for Wallet::Schema (that's the Wallet::Schema(3pm) situation, User Contributed Perl Documentation, if you're keeping track πŸ‘‘). it's the database schema + connector for the wallet system, and ngl it's a long one, so buckle up. part 1 starts now 🫢


✨ NAME ✨

Wallet::Schema β€” the database schema and connector for the wallet system, periodt πŸ’…


🌸 SYNOPSIS 🌸

like, the whole vibe in two lines:

use Wallet::Schema;
my $schema = Wallet::Schema->connect;

that's it. that's the girl. she loads, she connects, she slays ✨


πŸ¦‹ DESCRIPTION πŸ¦‹

okay so here's the tea β˜•: this class encapsulates the database schema for the wallet system. the documentation you're literally reading right now? it explains and comments the schema. and the class itself runs using the DBIx::Class module, no cap πŸŽ€

connect() will obtain the database connection info from the wallet configuration β€” see Wallet::Config for more details, bestie, go read it, iykyk πŸ‘‘

and the way that it's also automatically setting attributes for you?? obsessed 😭 it sets the RaiseError attribute to true, and the PrintError and AutoCommit attributes to false β€” which matches the assumptions made by the wallet database code. so like, don't come for her, she's just doing what the wallet database expects πŸ’…


more parts incoming, don't go anywhere bestie πŸ’–

πŸ’– SCHEMA πŸ’–

Okay bestie, this is the part where we talk about what's actually in the database. The whole thing is normalized into a bunch of little tables and honestly? It's giving organized queen. Let's go table by table. πŸŽ€


🌸 Normalization Tables 🌸

These bad boys hold the supported object types and the Perl classes they vibe with:

create table types
   (ty_name             varchar(16) primary key,
    ty_class            varchar(64));
insert into types (ty_name, ty_class)
    values ('file', 'Wallet::Object::File');
insert into types (ty_name, ty_class)
    values ('keytab', 'Wallet::Object::Keytab');

And here's where we keep the supported ACL schemes and their matching Perl classes:

create table acl_schemes
   (as_name             varchar(32) primary key,
    as_class            varchar(64));
insert into acl_schemes (as_name, as_class)
    values ('krb5', 'Wallet::ACL::Krb5');
insert into acl_schemes (as_name, as_class)
    values ('krb5-regex', 'Wallet::ACL::Krb5::Regex');
insert into acl_schemes (as_name, as_class)
    values ('ldap-attr', 'Wallet::ACL::LDAP::Attribute');
insert into acl_schemes (as_name, as_class)
    values ('ldap-attr-root', 'Wallet::ACL::LDAP::Attribute::Root');
insert into acl_schemes (as_name, as_class)
    values ('nested', 'Wallet::ACL::Nested');
insert into acl_schemes (as_name, as_class)
    values ('netdb', 'Wallet::ACL::NetDB');
insert into acl_schemes (as_name, as_class)
    values ('netdb-root', 'Wallet::ACL::NetDB::Root');

So like β€” if you went and extended the wallet to support extra object types or extra ACL schemes (main character energy fr), you're gonna wanna add more rows to these tables, mapping those types or schemes to the Perl classes that actually implement the object or ACL verifier APIs. That's the whole move. πŸ’…


πŸ” ACL Tables πŸ”

A wallet ACL is made up of zero or more ACL entries, and every entry is a scheme plus an identifier. The scheme says which check should happen, and the identifier is extra scheme-specific info. Each ACL points at entries in this table:

create table acls
   (ac_id               integer auto_increment primary key,
    ac_name             varchar(255) not null,
    unique (ac_name));

This one literally just keeps track of unique ACL identifiers, no cap. The actual data lives in:

create table acl_entries
   (ae_id               integer not null references acls(ac_id),
    ae_scheme           varchar(32)
        not null references acl_schemes(as_name),
    ae_identifier       varchar(255) not null,
    primary key (ae_id, ae_scheme, ae_identifier));
create index ae_id on acl_entries (ae_id);

ACLs can be referenced in the API by either the numeric ID or the human-readable name, but internally they're always referenced by numeric ID β€” that way you can rename them without needing some complicated data-modification situation. Slay, efficiency. ✨

Right now, the ACL named "ADMIN" (case-sensitive, bestie, watch the caps) is special-cased in the Wallet::Server code and gets granted global access. πŸ‘‘

Every single change made to any ACL in the database gets recorded in this table:

create table acl_history
   (ah_id               integer auto_increment primary key,
    ah_acl              integer not null,
    ah_name             varchar(255) default null,
    ah_action           varchar(16) not null,
    ah_scheme           varchar(32) default null,
    ah_identifier       varchar(255) default null,
    ah_by               varchar(255) not null,
    ah_from             varchar(255) not null,
    ah_on               datetime not null);
create index ah_acl on acl_history (ah_acl);

ah_action has to be one of "create", "destroy", "add", "remove", or "rename" (and nope, enums aren't used here β€” it's for compatibility with databases other than MySQL, iykyk).

For a change of type create, destroy, or rename, only the action, the ACL name (and for rename, that means the old ACL name from before the rename), and the trace records (by, from, and on) get stored. But when you change the actual lines of an ACL, the scheme and identifier of the line that got added or removed are included too.

ah_by stores the authenticated identity that made the change, ah_from stores the host they made it from, and ah_on stores the time it happened. Receipts, always. 🧾


πŸ“¦ Object Tables πŸ“¦

Every object stored in the wallet is represented by an entry in the objects table:

create table objects
   (ob_type             varchar(16)
        not null references types(ty_name),
    ob_name             varchar(255) not null,
    ob_owner            integer default null references acls(ac_id),
    ob_acl_get          integer default null references acls(ac_id),
    ob_acl_store        integer default null references acls(ac_id),
    ob_acl_show         integer default null references acls(ac_id),
    ob_acl_destroy      integer default null references acls(ac_id),
    ob_acl_flags        integer default null references acls(ac_id),
    ob_expires          datetime default null,
    ob_created_by       varchar(255) not null,
    ob_created_from     varchar(255) not null,
    ob_created_on       datetime not null,
    ob_stored_by        varchar(255) default null,
    ob_stored_from      varchar(255) default null,
    ob_stored_on        datetime default null,
    ob_downloaded_by    varchar(255) default null,
    ob_downloaded_from  varchar(255) default null,
    ob_downloaded_on    datetime default null,
    ob_comment          varchar(255) default null,
    primary key (ob_name, ob_type));
create index ob_owner on objects (ob_owner);
create index ob_expires on objects (ob_expires);

Okay so object names aren't globally unique β€” they're only unique within their type, which is why the table has a joint primary key. Not me explaining primary keys in the group chat πŸ’€ anyway. Each object has an owner and then up to five more specific ACLs. The owner gives permission for get, store, and show operations if no more specific ACL is set. It does not give permission for destroy or flags. Important!!

The ob_acl_flags ACL controls who can set flags on this object. And each object can have zero or more flags attached:

create table flags
   (fl_type             varchar(16)
        not null references objects(ob_type),
    fl_name             varchar(255)
        not null references objects(ob_name),
    fl_flag             enum('locked', 'unchanging')
        not null,
    primary key (fl_type, fl_name, fl_flag));
create index fl_object on flags (fl_type, fl_name);

Every change made to any object in the wallet database gets recorded here:

create table object_history
   (oh_id               integer auto_increment primary key,
    oh_type             varchar(16)
        not null references objects(ob_type),
    oh_name             varchar(255)
        not null references objects(ob_name),
    oh_action           varchar(16) not null,
    oh_field            varchar(16) default null,
    oh_type_field       varchar(255) default null,
    oh_old              varchar(255) default null,
    oh_new              varchar(255) default null,
    oh_by               varchar(255) not null,
    oh_from             varchar(255) not null,
    oh_on               datetime not null);
create index oh_object on object_history (oh_type, oh_name);

oh_action must be one of "create", "destroy", "get", "store", or "set". And oh_field must be one of "owner", "acl_get", "acl_store", "acl_show", "acl_destroy", "acl_flags", "expires", "flags", or "type_data". Again, no enums β€” it's for compatibility with databases other than MySQL.

For a change of type create, get, store, or destroy, only the action and the trace records (by, from, and on) are stored. For changes to columns or to the flags table, oh_field takes what attribute is changed, oh_from takes the previous value converted to a string, and oh_to takes the next value, similarly converted to a string. The special field value "type_data" gets used when type-specific data is changed, and in that case (and only that case) some type-specific name for the data being changed is stored in oh_type_field.

When you're clearing a flag, oh_old will have the name of the flag and oh_new will be null. When you're setting a flag, oh_old will be null and oh_new will have the name of the flag. Flip-flop behavior, love that for us. πŸ¦‹

oh_by stores the authenticated identity that made the change, oh_from stores the host they made it from, and oh_on stores the time it was made. Same energy as the ACL history, consistent queen.


πŸŽ€ Duo Backend Data πŸŽ€

Duo integration objects store some extra metadata about the integration, just to help with synchronization with Duo:

create table duo
   (du_name             varchar(255)
        not null references objects(ob_name),
    du_key              varchar(255) not null);
create index du_key on duo (du_key);

du_key holds the Duo integration key, which is the unique name of the integration within Duo. More data might get added later to represent the other possible settings within Duo, so like, watch this space. πŸ‘€


πŸ—οΈ Keytab Backend Data πŸ—οΈ

The keytab backend has stub support for synchronizing keys with an external system β€” although no external systems are currently supported, so it's giving placeholder. The permitted external systems are listed in a normalization table:

create table sync_targets
   (st_name             varchar(255) primary key);

and then the synchronization targets for a given keytab live in this table:

create table keytab_sync
   (ks_name             varchar(255)
        not null references objects(ob_name),
    ks_target           varchar(255)
        not null references sync_targets(st_name),
    primary key (ks_name, ks_target));
create index ks_name on keytab_sync (ks_name);

The keytab backend also supports restricting the allowable enctypes for a given keytab. The permitted enctypes are listed in a normalization table:

create table enctypes
   (en_name             varchar(255) primary key);

and then the restrictions for a given keytab are stored in this table:

create table keytab_enctypes
   (ke_name             varchar(255)
        not null references objects(ob_name),
    ke_enctype          varchar(255)
        not null references enctypes(en_name),
    primary key (ke_name, ke_enctype));
create index ke_name on keytab_enctypes (ke_name);

To actually use this functionality, you're gonna need to populate the enctypes table with the enctypes that a keytab may be restricted to. And bestie β€” currently there is no automated mechanism to do this. You're doing it by hand. πŸ’€


⭐ CLASS METHODS ⭐

  • connect() β€” Opens a new database connection and returns the database object. On any failure, it throws an exception. And unlike the DBI method, connect() takes no arguments β€” all the database connection info is derived from the wallet configuration. It just knows. ✨

πŸ‘€ SEE ALSO πŸ‘€

wallet-backend(8), Wallet::Config(3)

This module is part of the wallet system. The current version is available from <https://www.eyrie.org/~eagle/software/wallet/>.


πŸ’– AUTHOR πŸ’–

Bestie, meet the icon who wrote this whole thing:

Russ Allbery β€” <eagle@eyrie.org> πŸ¦…


πŸ‘‘ MAINTAINER πŸ‘‘

And the lovely human keeping it alive and thriving, no cap:

Bill MacAllister β€” <bill@ca-zephyr.org> πŸ’Œ


okay and that's literally the whole page, you made it to the end, I'm so proud of you πŸ₯ΉπŸ’…

This little masterpiece is from perl v5.40.0, dated 2024-11-12 β€” so it's giving vintage-but-still-iconic, fr fr. Go off, bestie πŸŽ€βœ¨

πŸ“¦ where this page comes from