# /etc/wallet/wallet.conf -- Wallet system configuration. -*- perl -*- # # Configuration for the wallet system as used at Stanford University. See # Wallet::Config(3) for complete details. Interesting features to note are # loading the database password from an external file and full implementations # of a naming policy check and default ACL rules. # # Written by Russ Allbery # Copyright 2007, 2008, 2009, 2010, 2012, 2013 # The Board of Trustees of the Leland Stanford Junior University # # See LICENSE for licensing terms. use Wallet::ACL; use Wallet::Database; $DB_DRIVER = 'mysql'; $DB_NAME = 'wallet'; $DB_HOST = 'localhost'; $DB_USER = 'wallet'; # Read the MySQL password from a separate file so that we don't have to commit # it to the Puppet repository. open (PASS, '<', '/etc/wallet/mysql-password') or die "cannot open /etc/wallet/mysql-password: $!\n"; $DB_PASSWORD = ; close PASS; chomp $DB_PASSWORD; # The maximum file object size is arbitrary, just something to keep anyone # from filling the disk. $FILE_BUCKET = '/srv/wallet/files'; $FILE_MAX_SIZE = 512 * 1024; $KEYTAB_KRBTYPE = 'Heimdal'; $KEYTAB_FILE = '/etc/wallet/keytab'; $KEYTAB_FLAGS = '-clearpolicy'; $KEYTAB_HOST = 'krb5-admin.stanford.edu'; $KEYTAB_PRINCIPAL = 'service/wallet@stanford.edu'; $KEYTAB_REALM = 'stanford.edu'; $KEYTAB_TMP = '/var/lib/wallet'; $KEYTAB_REMCTL_CACHE = '/var/lib/wallet/krb5cc_wallet'; $KEYTAB_REMCTL_HOST = 'kerberos1.stanford.edu'; $NETDB_REALM = 'stanford.edu'; $NETDB_REMCTL_CACHE = '/var/lib/wallet/krb5cc_wallet'; $NETDB_REMCTL_HOST = 'netdb-node-roles-rc.stanford.edu'; # Retrieve an existing ACL and check whether it contains a netdb-root member. # This is used to check if a default ACL is already present with a netdb-root # member so that we can return a default owner that matches. We only ever # increase the ACL from netdb to netdb-root, never degrade it, so this doesn't # pose a security problem. # # On any failure, just return an empty ACL to use the default. sub acl_has_netdb_root { my ($name) = @_; my $dbh = eval { Wallet::Database->connect }; return unless ($dbh and not $@); my $acl = eval { Wallet::ACL->new ($name, $dbh) }; return unless ($acl and not $@); for my $line ($acl->list) { return 1 if $line->[0] eq 'netdb-root'; } return; } # Map a file object name to a hostname and return it. Returns undef if this # file object name doesn't map to a hostname. sub _host_for_file { my ($name) = @_; my %allowed = map { $_ => 1 } qw(htpasswd ssh-rsa ssh-dsa ssl-key tivoli-key); my $allowed_regex = '(?:' . join ('|', sort keys %allowed) . ')'; if ($name !~ /^[^-]+-(.*)-$allowed_regex(?:-.*)?$/) { return; } my $host = $1; if ($host !~ /\./) { $host .= '.stanford.edu'; } return $host; } # Map a keytab object name to a hostname and return it. Returns undef if this # keytab principal name doesn't map to a hostname. sub _host_for_keytab { my ($name) = @_; my %allowed = map { $_ => 1 } qw(HTTP afpserver cifs ftp host imap ipp ldap lpr nfs pop postgres sieve smtp webauth xmpp); return unless $name =~ m,/,; my ($service, $host) = split ('/', $name, 2); return unless $allowed{$service}; if ($host !~ /\./) { $host .= '.stanford.edu'; } return $host; } # The default owner of host-based objects should be the host keytab and the # NetDB ACL for that host, with one twist. If the creator of a new node is # using a root instance, we want to require everyone managing that node be # using root instances by default (this will do the right thing for Unix # Systems hosts). sub default_owner { my ($type, $name) = @_; my $realm = 'stanford.edu'; my %host_for = ( keytab => \&_host_for_keytab, file => \&_host_for_file, ); return unless defined $host_for{$type}; my $host = $host_for{$type}->($name); return unless $host; my $acl_name = "host/$host"; my @acl; if ($ENV{REMOTE_USER} =~ m,/root, or acl_has_netdb_root ($acl_name)) { @acl = ([ 'netdb-root', $host ], [ 'krb5', "host/$host\@$realm" ]); } else { @acl = ([ 'netdb', $host ], [ 'krb5', "host/$host\@$realm" ]); } return ($acl_name, @acl); } # Enforce a naming policy. Host-based keytabs must have fully-qualified # hostnames, limit the acceptable characters for service/* keytabs, and # enforce our naming constraints on */cgi principals. # # Also use this function to require that IDG staff always do implicit object # creation using a */root instance. sub verify_name { my ($type, $name, $user) = @_; my %host = map { $_ => 1 } qw(HTTP afpserver cifs ftp http host ident imap ipp ldap lpr nfs pop postgres sieve smtp uniengd webauth xmpp); my %staff; if (open (STAFF, '<', '/etc/remctl/acl/its-idg')) { local $_; while () { s/^\s+//; s/\s+$//; next if m,/root\@,; $staff{$_} = 1; } close STAFF; } # Check for a staff member not using their root instance. if (defined ($user) && $staff{$user}) { return 'use a */root instance for wallet object creation'; } # Check keytab naming conventions. if ($type eq 'keytab') { if ($name !~ m,^[a-zA-Z0-9_-]+/[a-z0-9.-]+$,) { return "invalid principal name $name"; } my ($principal, $instance) = ($name =~ m,^([a-zA-Z0-9_-]+)/([a-z0-9.-]+)$,); unless (defined ($principal) && defined ($instance)) { return "invalid principal name $name"; } if ($host{$principal} and $principal ne 'http') { if ($instance !~ /^[a-z0-9-]+\.[a-z0-9.-]+$/) { return "host name $instance is not fully qualified"; } } elsif ($principal eq 'service') { if ($instance !~ /^[a-z0-9-]+$/) { return "invalid service principal name $name"; } } elsif ($instance eq 'cgi') { if ($principal !~ /^[a-z][a-z0-9]{1,7}$/ and $principal !~ /^(class|dept|group)-[a-z0-9_-]+$/) { return "invalid CGI principal name $name"; } } } # Check file object naming conventions. if ($type eq 'file') { my %groups = map { $_ => 1 } qw(apps crcsg gsb idg sysadmin sulair unix vast); my %types = map { $_ => 1 } qw(config db gpg-key htpasswd password properties ssh-rsa ssh-dsa ssl-key ssl-keystore ssl-pkcs12 tivoli-key); if ($name !~ m,^[a-zA-Z0-9_.-]+$,) { return "invalid file object $name"; } my $group_regex = '(?:' . join ('|', sort keys %groups) . ')'; my $type_regex = '(?:' . join ('|', sort keys %types) . ')'; if ($name !~ /^$group_regex-/) { return "no recognized owning group in $name"; } elsif ($name !~ /^$group_regex-.*-$type_regex(-.*)?$/) { return "invalid file object name $name"; } } # Success. return; } 1;