Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

make cpanm secure by default #674

Open
wants to merge 5 commits into
base: devel
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions App-cpanminus/lib/App/cpanminus.pm
Original file line number Diff line number Diff line change
Expand Up @@ -105,16 +105,16 @@ Module::Build (core in 5.10)

=head2 How does cpanm get/parse/update the CPAN index?

It queries the CPAN Meta DB site at L<http://cpanmetadb.plackperl.org/>.
It queries the CPAN Meta DB site at L<https://cpanmetadb.plackperl.org/>.
The site is updated at least every hour to reflect the latest changes
from fast syncing mirrors. The script then also falls back to query the
module at L<http://metacpan.org/> using its search API.
module at L<https://metacpan.org/> using its search API.

Upon calling these API hosts, cpanm (1.6004 or later) will send the
local perl versions to the server in User-Agent string by default. You
can turn it off with C<--no-report-perl-version> option. Read more
about the option with L<cpanm>, and read more about the privacy policy
about this data collection at L<http://cpanmetadb.plackperl.org/#privacy>
about this data collection at L<https://cpanmetadb.plackperl.org/#privacy>

Fetched files are unpacked in C<~/.cpanm> and automatically cleaned up
periodically. You can configure the location of this with the
Expand Down Expand Up @@ -270,7 +270,7 @@ Arnfjord Bjarmason, Eric Wilhelm, Florian Ragwitz and xaicron.

=over 4

=item L<http://github.com/miyagawa/cpanminus> - source code repository, issue tracker
=item L<https://github.com/miyagawa/cpanminus> - source code repository, issue tracker

=item L<irc://irc.perl.org/#cpanm> - discussions about cpanm and its related tools

Expand Down
23 changes: 15 additions & 8 deletions App-cpanminus/script/cpanm.PL
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,14 @@ cpanm - get, unpack build and install modules from CPAN

cpanm Test::More # install Test::More
cpanm MIYAGAWA/Plack-0.99_05.tar.gz # full distribution path
cpanm http://example.org/LDS/CGI.pm-3.20.tar.gz # install from URL
cpanm https://example.org/LDS/CGI.pm-3.20.tar.gz # install from URL
cpanm ~/dists/MyCompany-Enterprise-1.00.tar.gz # install from a local file
cpanm --interactive Task::Kensho # Configure interactively
cpanm . # install from local directory
cpanm --installdeps . # install all the deps for the current directory
cpanm -L extlib Plack # install Plack and all non-core deps into extlib
cpanm --mirror http://cpan.cpantesters.org/ DBI # use the fast-syncing mirror
cpanm --from https://cpan.metacpan.org/ Plack # use only the HTTPS mirror
cpanm --mirror https://cpan.cpantesters.org/ DBI # use the fast-syncing mirror
cpanm --from https://cpan.metacpan.org/ Plack # use a different mirror

=head1 COMMANDS

Expand All @@ -60,7 +60,7 @@ will all work as you expect.
cpanm Plack/Request.pm
cpanm MIYAGAWA/Plack-1.0000.tar.gz
cpanm /path/to/Plack-1.0000.tar.gz
cpanm http://cpan.metacpan.org/authors/id/M/MI/MIYAGAWA/Plack-0.9990.tar.gz
cpanm https://cpan.metacpan.org/authors/id/M/MI/MIYAGAWA/Plack-0.9990.tar.gz
cpanm git://github.com/plack/Plack.git

Additionally, you can use the notation using C<~> and C<@> to specify
Expand Down Expand Up @@ -213,7 +213,7 @@ the behaviour from before version 1.7023
=item --mirror

Specifies the base URL for the CPAN mirror to use, such as
C<http://cpan.cpantesters.org/> (you can omit the trailing slash). You
C<https://cpan.cpantesters.org/> (you can omit the trailing slash). You
can specify multiple mirror URLs by repeating the command line option.

You can use a local directory that has a CPAN mirror structure
Expand All @@ -224,7 +224,7 @@ scheme), it is considered as a file scheme as well.
cpanm --mirror file:///path/to/mirror
cpanm --mirror ~/minicpan # Because shell expands ~ to /home/user

Defaults to C<http://www.cpan.org/>.
Defaults to C<https://www.cpan.org/>.

=item --mirror-only

Expand Down Expand Up @@ -256,7 +256,7 @@ B<Tip:> It might be useful if you name these options with your shell
aliases, like:

alias minicpanm='cpanm --from ~/minicpan'
alias darkpan='cpanm --from http://mycompany.example.com/DPAN'
alias darkpan='cpanm --from https://mycompany.example.com/DPAN'

=item --mirror-index

Expand Down Expand Up @@ -555,9 +555,16 @@ Defaults to true (man pages generated) unless C<-L|--local-lib-contained>
option is supplied in which case it's set to false. You can disable
it with C<--no-man-pages>.

=item --insecure

By default, cpanm only uses HTTPS. If your environment lacks TLS
support, you can consider at your own risk to use HTTP instead by
using the C<--insecure> flag.
Be aware that when using that argument all requests will use HTTP.

=item --lwp

Uses L<LWP> module to download stuff over HTTP. Defaults to true, and
Uses L<LWP> module to download stuff over HTTPS. Defaults to true, and
you can say C<--no-lwp> to disable using LWP, when you want to upgrade
LWP from CPAN on some broken perl systems.

Expand Down
2 changes: 1 addition & 1 deletion Menlo-Legacy/META.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"perl_5"
],
"meta-spec" : {
"url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec",
"url" : "https://metacpan.org/pod/CPAN::Meta::Spec",
"version" : 2
},
"name" : "Menlo-Legacy",
Expand Down
118 changes: 94 additions & 24 deletions Menlo-Legacy/lib/Menlo/CLI/Compat.pm
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ sub new {
mirrors => [],
mirror_only => undef,
mirror_index => undef,
cpanmetadb => "http://cpanmetadb.plackperl.org/v1.0/",
cpanmetadb => "https://cpanmetadb.plackperl.org/v1.0/",
perl => $^X,
argv => [],
local_lib => undef,
Expand All @@ -79,6 +79,7 @@ sub new {
try_lwp => 1,
try_wget => 1,
try_curl => 1,
use_http => 0,
uninstall_shadows => ($] < 5.012),
skip_installed => 1,
skip_satisfied => 0,
Expand Down Expand Up @@ -152,6 +153,7 @@ sub parse_options {
push @ARGV, grep length, split /\s+/, $self->env('OPT');
push @ARGV, @_;

my $custom_cpanmetadb;
Getopt::Long::Configure("bundling");
Getopt::Long::GetOptions(
'f|force' => sub { $self->{skip_installed} = 0; $self->{force} = 1 },
Expand Down Expand Up @@ -182,7 +184,7 @@ sub parse_options {
$self->{mirrors} = [$_[1]];
$self->{mirror_only} = 1;
},
'cpanmetadb=s' => \$self->{cpanmetadb},
'cpanmetadb=s' => \$custom_cpanmetadb,
'cascade-search!' => \$self->{cascade_search},
'prompt!' => \$self->{prompt},
'installdeps' => \$self->{installdeps},
Expand All @@ -199,6 +201,7 @@ sub parse_options {
'lwp!' => \$self->{try_lwp},
'wget!' => \$self->{try_wget},
'curl!' => \$self->{try_curl},
'insecure!' => \$self->{use_http},
'auto-cleanup=s' => \$self->{auto_cleanup},
'man-pages!' => \$self->{pod2man},
'scandeps' => \$self->{scandeps},
Expand Down Expand Up @@ -233,6 +236,14 @@ sub parse_options {
$self->{load_from_stdin} = 1;
}

if ($custom_cpanmetadb) {
$self->{cpanmetadb} = $custom_cpanmetadb;
$self->{has_custom_cpanmetadb} = 1;
}
else {
$self->{cpanmetadb} =~ s!^https:!http:! if $self->{use_http};
}

$self->{argv} = \@ARGV;
}

Expand Down Expand Up @@ -453,7 +464,7 @@ sub search_common {
$self->chat("Found $found->{module} $found->{module_version} which doesn't satisfy $want_version.\n");
}
}

return;
}

Expand Down Expand Up @@ -609,14 +620,15 @@ Options:
-v,--verbose Turns on chatty output
-q,--quiet Turns off the most output
--interactive Turns on interactive configure (required for Task:: modules)
-f,--force force install
--insecure Use HTTP-only requests instead of HTTPS
-f,--force Force install
-n,--notest Do not run unit tests
--test-only Run tests only, do not install
-S,--sudo sudo to run install commands
--installdeps Only install dependencies
--showdeps Only display direct dependencies
--reinstall Reinstall the distribution even if you already have the latest version installed
--mirror Specify the base URL for the mirror (e.g. http://cpan.cpantesters.org/)
--mirror Specify the base URL for the mirror (e.g. https://cpan.cpantesters.org/)
--mirror-only Use the mirror's index file instead of the CPAN Meta DB
-M,--from Use only this mirror base URL and its index file
--prompt Prompt when configure/build/test fails
Expand All @@ -626,7 +638,7 @@ Options:
--auto-cleanup Number of days that cpanm's work directories expire in. Defaults to 7

Commands:
--self-upgrade upgrades itself
--self-upgrade Upgrades itself
--info Displays distribution info on CPAN
--look Opens the distribution with your SHELL
-U,--uninstall Uninstalls the modules (EXPERIMENTAL)
Expand All @@ -636,18 +648,18 @@ Examples:

cpanm Test::More # install Test::More
cpanm MIYAGAWA/Plack-0.99_05.tar.gz # full distribution path
cpanm http://example.org/LDS/CGI.pm-3.20.tar.gz # install from URL
cpanm https://example.org/LDS/CGI.pm-3.20.tar.gz # install from URL
cpanm ~/dists/MyCompany-Enterprise-1.00.tar.gz # install from a local file
cpanm --interactive Task::Kensho # Configure interactively
cpanm . # install from local directory
cpanm --installdeps . # install all the deps for the current directory
cpanm -L extlib Plack # install Plack and all non-core deps into extlib
cpanm --mirror http://cpan.cpantesters.org/ DBI # use the fast-syncing mirror
cpanm --mirror https://cpan.cpantesters.org/ DBI # use the fast-syncing mirror
cpanm -M https://cpan.metacpan.org App::perlbrew # use only this secure mirror and its index

You can also specify the default options in PERL_CPANM_OPT environment variable in the shell rc:

export PERL_CPANM_OPT="--prompt --reinstall -l ~/perl --mirror http://cpan.cpantesters.org"
export PERL_CPANM_OPT="--prompt --reinstall -l ~/perl --mirror https://cpan.cpantesters.org"

Type `man cpanm` or `perldoc cpanm` for the more detailed explanation of the options.

Expand Down Expand Up @@ -977,7 +989,7 @@ sub append_args {
my($self, $cmd, $phase) = @_;

return $cmd if ref $cmd ne 'ARRAY';

if (my $args = $self->{build_args}{$phase}) {
$cmd = join ' ', Menlo::Util::shell_quote(@$cmd), $args;
}
Expand Down Expand Up @@ -1163,12 +1175,22 @@ sub chdir {
sub configure_mirrors {
my $self = shift;
unless (@{$self->{mirrors}}) {
$self->{mirrors} = [ 'http://www.cpan.org' ];
$self->{mirrors} = [
($self->{use_http} ? 'http' : 'https') . '://www.cpan.org'
];
}

my $warned;
for (@{$self->{mirrors}}) {
s!^/!file:///!;
s!/$!!;

if (m/^http:/ && !$self->{use_http} && !$warned) {
warn "WARNING: you are using a non-HTTPS mirror, which is considered insecure. To remove this message, please pass the --insecure flag.\n" if !$warned;
$warned = 1;
}
}
return;
}

sub self_upgrade {
Expand Down Expand Up @@ -1637,6 +1659,7 @@ sub cpan_module_common {

my $mirrors = $self->{mirrors};
if ($match->{download_uri}) {
$match->{download_uri} =~ s!^https:!http:! if $self->{use_http};
(my $mirror = $match->{download_uri}) =~ s!/authors/id/.*$!!;
$mirrors = [$mirror];
}
Expand Down Expand Up @@ -1688,7 +1711,7 @@ sub cpan_dist {
sub git_uri {
my ($self, $uri) = @_;

# similar to http://www.pip-installer.org/en/latest/logic.html#vcs-support
# similar to https://www.pip-installer.org/en/latest/logic.html#vcs-support
# git URL has to end with .git when you need to use pin @ commit/tag/branch

($uri, my $commitish) = split /(?<=\.git)@/i, $uri, 2;
Expand Down Expand Up @@ -2650,11 +2673,39 @@ sub DESTROY {

sub mirror {
my($self, $uri, $local) = @_;
if ($uri =~ /^file:/) {
$self->file_mirror($uri, $local);
} else {
$self->{http}->mirror($uri, $local);

die( "mirror: Undefined URI\n" ) unless defined $uri && length $uri;

if ( $uri =~ /^file:/) {
return $self->file_mirror($uri, $local);
}

# HTTPTinyish does not provide an option to disable
# certificates check, let's switch to http on demand.
$uri =~ s/^https:/http:/ if $self->{use_http};

my $reply = $self->{http}->mirror($uri, $local);

if ( $uri =~ /^https:/ && ref $reply
&& $reply->{status} && $reply->{status} == 599
&& $reply->{content}
) {
my $invalid_cert;
if ( ref($self->{http}) =~ m{(?:Curl|HTTPTiny|Wget)} ) {
$invalid_cert = 1 if $reply->{content} =~ m{certificate}mi;
} elsif ( ref($self->{http}) =~ m{LWP} ) {
$invalid_cert = 1 if $reply->{content} =~ m{Can't connect.+?:443}mi;
}
if ( $invalid_cert ) {
die <<"DIE";
TLS issue found while fetching $uri:\n
$reply->{content}\n
Please verify/update your certificates. You may also force an HTTP-only mirror or use the --insecure flag.
DIE
}
}

return $reply;
}

sub untar { $_[0]->{_backends}{untar}->(@_) };
Expand Down Expand Up @@ -2696,21 +2747,41 @@ sub file_mirror {
sub configure_http {
my $self = shift;

require HTTP::Tinyish;

my @try = qw(HTTPTiny);
unshift @try, 'Wget' if $self->{try_wget};
unshift @try, 'Curl' if $self->{try_curl};
unshift @try, 'LWP' if $self->{try_lwp};

my @protocol = ('http');
push @protocol, 'https'
if grep /^https:/, @{$self->{mirrors}};
if (!$self->{use_http} || $self->{cpanmetadb} =~ /^https:/ || (grep /^https:/, @{$self->{mirrors}})) {
push @protocol, 'https';
}

my $backend = $self->get_http_backend(\@try, \@protocol);

# fallback to http-only if we failed using https with default options:
if (!$backend && !$self->{use_http} && !@{$self->{mirrors}} && (!$self->{has_custom_cpanmetadb} || $self->{cpanmetadb} =~ /^http:/)) {
$self->diag('WARNING: TLS support not found. Falling back to insecure HTTP-only requests');
$self->{use_http} = 1;
@protocol = ('http');
$backend = $self->get_http_backend(\@try, \@protocol);
}

if ( !$backend ) {
$self->diag_fail( join( ', ', @protocol )." not supported by available HTTP Clients." );
}

$backend->new(agent => "Menlo/$Menlo::VERSION", verify_SSL => 1);
}

sub get_http_backend {
my ($self, $tries, $protocols) = @_;

require HTTP::Tinyish;
my $backend;
for my $try (map "HTTP::Tinyish::$_", @try) {
for my $try (map "HTTP::Tinyish::$_", @$tries) {
if (my $meta = HTTP::Tinyish->configure_backend($try)) {
if ((grep $try->supports($_), @protocol) == @protocol) {
if ((grep $try->supports($_), @$protocols) == @$protocols) {
for my $tool (sort keys %$meta){
(my $desc = $meta->{$tool}) =~ s/^(.*?)\n.*/$1/s;
$self->chat("You have $tool: $desc\n");
Expand All @@ -2720,8 +2791,7 @@ sub configure_http {
}
}
}

$backend->new(agent => "Menlo/$Menlo::VERSION", verify_SSL => 1);
return $backend;
}

sub init_tools {
Expand Down
2 changes: 1 addition & 1 deletion Menlo/Changes
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
See http://github.com/miyagawa/cpanminus/ for the latest development.
See https://github.com/miyagawa/cpanminus/ for the latest development.

{{$NEXT}}

Expand Down
2 changes: 1 addition & 1 deletion Menlo/META.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"perl_5"
],
"meta-spec" : {
"url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec",
"url" : "https://search.cpan.org/perldoc?CPAN::Meta::Spec",
"version" : 2
},
"name" : "Menlo",
Expand Down
Loading
Loading