summaryrefslogtreecommitdiff
path: root/examples/stanford.conf
blob: b0533bda57eb9fcbc867ea3f0cb997cd3da2e533 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
# /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 <rra@stanford.edu>
# 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 = <PASS>;
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 (<STAFF>) {
            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;