File Coverage

blib/lib/Role/Kerberos.pm
Criterion Covered Total %
statement 15 17 88.2
branch n/a
condition n/a
subroutine 6 6 100.0
pod n/a
total 21 23 91.3


line stmt bran cond sub pod time code
1             package Role::Kerberos;
2              
3 1     1   21298 use 5.010;
  1         4  
4 1     1   6 use strict;
  1         3  
  1         31  
5 1     1   5 use warnings FATAL => 'all';
  1         7  
  1         54  
6              
7 1     1   838 use Moo::Role;
  1         22406  
  1         7  
8 1     1   1171 use namespace::clean;
  1         25892  
  1         8  
9              
10 1     1   951 use Authen::Krb5 ();
  0            
  0            
11             use Scalar::Util ();
12             use Carp ();
13             use Try::Tiny ();
14              
15             # Authen::Krb5 contains a global, presumably non-threadsafe pointer to
16             # this execution context. This is the best way I can muster dealing
17             # with it.
18              
19             BEGIN {
20             Authen::Krb5::init_context();
21             }
22              
23             END {
24             Authen::Krb5::free_context();
25             }
26              
27             sub _is_really {
28             my ($x, $class) = @_;
29             defined $x and ref $x and Scalar::Util::blessed($x) and $x->isa($class);
30             }
31              
32             sub _k5err {
33             Carp::croak(@_, ': ', Authen::Krb5::error());
34             }
35              
36             =head1 NAME
37              
38             Role::Kerberos - A role for managing Kerberos 5 credentials
39              
40             =head1 VERSION
41              
42             Version 0.01_01
43              
44             =cut
45              
46             our $VERSION = '0.01_01';
47              
48             =head1 SYNOPSIS
49              
50             use Moo;
51             with 'Role::Kerberos';
52              
53             # go nuts
54              
55             =head1 DESCRIPTION
56              
57             L is kind of unwieldy. L is too
58             simple (no keytabs). L requires too much
59             effort (can't specify keytabs/ccaches outside of environment
60             variables) and L hasn't been touched in 13 years.
61              
62             The purpose of this module is to enable you to strap Kerberos onto an
63             existing (L[L]) object, as any role is apt to do.
64              
65             =head1 METHODS
66              
67             =head2 new
68              
69             =head3 Parameters/Accessors
70              
71             =over 4
72              
73             =item realm
74              
75             The default realm.
76              
77             =cut
78              
79             around BUILDARGS => sub {
80             my $orig = shift;
81             my $class = shift;
82             my %p;
83             if (@_ and ref $_[0] eq 'HASH') {
84             %p = %{$_[0]};
85             }
86             else {
87             %p = @_;
88             }
89              
90             Carp::croak('Must supply at least a principal')
91             unless defined $p{principal} and $p{principal} ne '';
92              
93             if ($p{principal} =~ /@/) {
94             $p{principal} = _coerce_principal($p{principal});
95             $p{realm} ||= $p{principal}->realm;
96             }
97             else {
98             $p{realm} ||= Authen::Krb5::get_default_realm();
99             $p{principal} = sprintf '%s@%s', @p{qw(principal realm)};
100             }
101              
102             $orig->($class, %p);
103             };
104              
105             has realm => (
106             is => 'rw',
107             lazy => 1,
108             default => sub { Authen::Krb5::get_default_realm(); },
109             );
110              
111             =item principal
112              
113             The default principal. Can (should) also contain a realm.
114              
115             =cut
116              
117             sub _coerce_principal {
118             my $n = shift;
119             return $n if _is_really($n, 'Authen::Krb5::Principal');
120              
121             my $r = shift || Authen::Krb5::get_default_realm();
122              
123             $n = sprintf '%s@%s', $n, $r unless $n =~ /@/;
124              
125             Authen::Krb5::parse_name($n)
126             or _k5err("Could not resolve principal $n");
127             }
128              
129             has principal => (
130             is => 'ro',
131             isa => sub { _is_really(shift, 'Authen::Krb5::Principal') },
132             required => 1,
133             trigger => sub { $_[0]->realm($_[0]->principal->realm) },
134             coerce => \&_coerce_principal,
135             );
136              
137             =item keytab
138              
139             A keytab, if other than C<$ENV{KRB5_KTNAME}>.
140              
141             =cut
142              
143             sub _coerce_kt {
144             my $val = shift;
145             return $val if _is_really($val, 'Authen::Krb5::Keytab');
146              
147             $val = "FILE:$val" unless $val =~ /^[^:]+:/;
148              
149             Authen::Krb5::kt_resolve($val) or _k5err("Could not load keytab $val");
150             }
151              
152             has keytab => (
153             is => 'ro',
154             isa => sub { _is_really(shift, 'Authen::Krb5::Keytab') },
155             lazy => 1,
156             coerce => \&_coerce_kt,
157             default => sub {
158             Authen::Krb5::kt_default() or _k5err("Could not load default keytab");
159             },
160             );
161              
162             =item ccache
163              
164             The locator (e.g. file path) of a credential cache.
165              
166             =cut
167              
168             has ccache => (
169             is => 'ro',
170             lazy => 1,
171             coerce => sub {
172             my $val = shift;
173             return $val if _is_really($val, 'Authen::Krb5::Ccache');
174              
175             $val = "FILE:$val" unless $val =~ /^FILE:/i;
176              
177             my $kt = Authen::Krb5::cc_resolve($val)
178             or _k5err("Could not load credential cache $val");
179             },
180             default => sub {
181             Authen::Krb5::cc_default()
182             or _k5err("Could not resolve default credentials cache");
183             },
184             );
185              
186             =back
187              
188             =head2 kinit %PARAMS
189              
190             Log in to Kerberos. Parameters are optional
191              
192             =over 4
193              
194             =item principal
195              
196             =item realm
197              
198             =item password
199              
200             =item keytab
201              
202             =item service
203              
204             =back
205              
206             =cut
207              
208             sub kinit {
209             my $self = shift;
210             my %p = @_;
211              
212             $p{realm} ||= $self->realm;
213             $p{principal} = $p{principal}
214             ? _coerce_principal(@p{qw(principal realm)}) : $self->principal;
215              
216             my $tgt;
217             if (defined $p{password}) {
218             my @a = @p{qw(principal password)};
219             push @a, $p{service} if defined $p{service};
220              
221             $tgt = Authen::Krb5::get_init_creds_password(@a)
222             or _k5err('Failed to get TGT');
223             }
224             else {
225             $p{keytab} = $p{keytab} ? _coerce_kt($p{keytab}) : $self->keytab;
226             my @a = @p{qw(principal keytab)};
227             push @a, $p{service} if defined $p{service};
228              
229             $tgt = Authen::Krb5::get_init_creds_keytab(@a)
230             or _k5err('Failed to get TGT');
231             }
232              
233             my $cc = $self->ccache;
234             $cc->initialize($p{principal});
235             $cc->store_cred($tgt);
236             }
237              
238             =head2 klist %PARAMS
239              
240             =cut
241              
242             sub klist {
243             my $self = shift;
244              
245             my $cc = $self->ccache;
246             my $p = $self->principal;
247             my @out;
248             if (my $cursor = $cc->start_seq_get) {
249             while (my $obj = $cc->next_cred($cursor)) {
250             push @out, {
251             principal => $obj->client,
252             service => $obj->server,
253             auth => $obj->authtime,
254             start => $obj->starttime,
255             end => $obj->endtime,
256             renew => $obj->renew_till,
257             ticket => $obj->ticket,
258             # this segfaults
259             # keyblock => $obj->keyblock,
260             };
261             }
262             $cc->end_seq_get($cursor);
263             }
264              
265             return unless @out;
266             wantarray ? @out : \@out;
267             }
268              
269             =head2 kdestroy
270              
271             Destroy the credentials cache (if there is something to destroy).
272              
273             =cut
274              
275             sub kdestroy {
276             my $self = shift;
277             $self->ccache->destroy if $self->klist;
278             }
279              
280             # XXX do we actually want this to happen?
281             # sub DEMOLISH {
282             # $_[0]->kdestroy;
283             # }
284              
285             # sub DEMOLISH {
286             # warn 'lol';
287             # }
288              
289             sub DEMOLISH {
290             my $self = shift;
291             for my $entry ($self->klist) {
292             delete $entry->{keyblock};
293             }
294             }
295              
296             =head1 AUTHOR
297              
298             Dorian Taylor, C<< >>
299              
300             =head1 SEE ALSO
301              
302             =over 4
303              
304             =item L
305              
306             =item L
307              
308             =back
309              
310              
311             =head1 BUGS
312              
313             Please report any bugs or feature requests to C
314             rt.cpan.org>, or through the web interface at
315             L. I
316             will be notified, and then you'll automatically be notified of
317             progress on your bug as I make changes.
318              
319             =head1 LICENSE AND COPYRIGHT
320              
321             Copyright 2015 Dorian Taylor.
322              
323             Licensed under the Apache License, Version 2.0 (the "License"); you
324             may not use this file except in compliance with the License. You may
325             obtain a copy of the License at
326             L.
327              
328             Unless required by applicable law or agreed to in writing, software
329             distributed under the License is distributed on an "AS IS" BASIS,
330             WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
331             implied. See the License for the specific language governing
332             permissions and limitations under the License.
333              
334             =cut
335              
336             1; # End of Role::Kerberos