Skip to content

Commit

Permalink
Make openqa-clone-job work with authenticated assets downloads
Browse files Browse the repository at this point in the history
Configure key/secret auth in LWP user agent for the host we download assets
from (using credentials from `client.conf`, special CLI arguments are not
supported yet). This change is reusing code from openQA user agent for
parsing `client.conf` and adding headers.

Related ticket: https://progress.opensuse.org/issues/170380
  • Loading branch information
Martchus committed Jan 16, 2025
1 parent ed0fc96 commit c612650
Show file tree
Hide file tree
Showing 3 changed files with 62 additions and 28 deletions.
21 changes: 17 additions & 4 deletions lib/OpenQA/Script/CloneJob.pm
Original file line number Diff line number Diff line change
Expand Up @@ -181,12 +181,24 @@ sub split_jobid ($url_string) {
return ($host_url, $jobid);
}

sub create_url_handler ($options) {
sub create_lwp_user_agent ($host, $options) {
my $ua = LWP::UserAgent->new;
$ua->timeout(10);
$ua->env_proxy;
$ua->show_progress(1) if ($options->{'show-progress'});
$ua->show_progress(1) if $options->{'show-progress'};
return $ua unless my $cfg = OpenQA::UserAgent::open_config_file($host);

my $apikey = ($cfg->val($host, 'key'))[-1];
my $apisecret = ($cfg->val($host, 'secret'))[-1];
$ua->add_handler(request_prepare => sub ($request, $ua, $handler) {
OpenQA::UserAgent::add_auth_headers($request, Mojo::URL->new($request->uri), $apikey, $apisecret);
}) if $apikey && $apisecret;

return $ua;
}

sub create_url_handler ($options) {
# configure user agent for destination host (usually localhost)
my $local_url = OpenQA::Client::url_from_host($options->{host});
$local_url->path('/api/v1/jobs');
my $local = OpenQA::Client->new(
Expand All @@ -196,10 +208,11 @@ sub create_url_handler ($options) {
die "API key/secret for '$options->{host}' missing. Checkout '$0 --help' for the config file syntax/lookup.\n"
if !$options->{'export-command'} && !($local->apikey && $local->apisecret);

# configure user agents for the source host (usually a remote host)
my $remote_url = OpenQA::Client::url_from_host($options->{from});
$remote_url->path('/api/v1/jobs');
my $remote = OpenQA::Client->new(api => $options->{host});

my $remote = OpenQA::Client->new(api => $remote_url->host);
my $ua = create_lwp_user_agent($remote_url->host, $options);
return {ua => $ua, local => $local, local_url => $local_url, remote => $remote, remote_url => $remote_url};
}

Expand Down
55 changes: 31 additions & 24 deletions lib/OpenQA/UserAgent.pm
Original file line number Diff line number Diff line change
Expand Up @@ -27,49 +27,56 @@ sub new {
# Some urls might redirect to https and then there are internal redirects for assets
$self->max_redirects(3);

$self->on(start => sub ($ua, $tx) { $self->_add_auth_headers($ua, $tx) });
$self->on(start => sub ($ua, $tx) { $self->_add_headers($tx) });

#read proxy environment variables
$self->proxy->detect;

return $self;
}

sub configure_credentials ($self, $host) {
sub open_config_file ($host) {
return undef unless $host;
my @cfgpaths = ($ENV{OPENQA_CONFIG} // glob('~/.config/openqa'), '/etc/openqa');
for my $path (@cfgpaths) {
my $file = $path . '/client.conf';
next unless $file && -r $file;
my $cfg = Config::IniFiles->new(-file => $file) || last;
last unless $cfg->SectionExists($host);
for my $i (qw(key secret)) {
my $attr = "api$i";
next if $self->$attr;
# Fetch all the values in the file and keep the last one
my @values = $cfg->val($host, $i);
next unless my $val = $values[-1];
$val =~ s/\s+$//; # remove trailing whitespace
$self->$attr($val);
}
last;
next unless -r $file;
my $cfg = Config::IniFiles->new(-file => $file);
return $cfg && $cfg->SectionExists($host) ? $cfg : undef;
}
return undef;
}

sub configure_credentials ($self, $host) {
return undef unless my $cfg = open_config_file($host);
for my $i (qw(key secret)) {
my $attr = "api$i";
next if $self->$attr;
# Fetch all the values in the file and keep the last one
my @values = $cfg->val($host, $i);
next unless my $val = $values[-1];
$val =~ s/\s+$//; # remove trailing whitespace
$self->$attr($val);
}
}

sub _add_auth_headers ($self, $ua, $tx) {
sub add_auth_headers ($headers, $url, $apikey, $apisecret) {
my $timestamp = time;
my $headers = $tx->req->headers;
$headers->accept('application/json') unless defined $headers->accept;
$headers->header('X-API-Microtime', $timestamp);
if ($self->apisecret && $self->apikey) {
$headers->header('X-API-Key', $self->apikey);
$headers->header('X-API-Hash', hmac_sha1_sum($self->_path_query($tx) . $timestamp, $self->apisecret));
if ($apikey && $apisecret) {
$headers->header('X-API-Key', $apikey);
$headers->header('X-API-Hash', hmac_sha1_sum(_path_query($url) . $timestamp, $apisecret));
}
}

sub _path_query {
my $self = shift;
my $url = shift->req->url;
sub _add_headers ($self, $tx) {
my $req = $tx->req;
my $headers = $req->headers;
$headers->accept('application/json') unless defined $headers->accept;
add_auth_headers($headers, $req->url, $self->apikey, $self->apisecret);
}

sub _path_query ($url) {
my $query = $url->query->to_string;
# as use this for hashing, we need to make sure the query is escaping
# space the same as the mojo url parser.
Expand Down
14 changes: 14 additions & 0 deletions t/35-script_clone_job.t
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,12 @@ use Test::Output qw(combined_like combined_unlike output_from);
use Test::MockObject;
use Test::MockModule;
use OpenQA::Script::CloneJob;
use HTTP::Response;
use Mojo::JSON qw(decode_json);
use Mojo::URL;
use Mojo::File 'tempdir';
use Mojo::Transaction;
use Scalar::Util qw(looks_like_number);

# define fake client
{
Expand Down Expand Up @@ -426,4 +428,16 @@ subtest 'overall cloning with parallel and chained dependencies' => sub {
};
};

subtest 'auth with lwp' => sub {
note 'config path: ' . ($ENV{OPENQA_CONFIG} = "$FindBin::Bin/data");
my $ua = OpenQA::Script::CloneJob::create_lwp_user_agent('testapi', {});
$ua->add_handler(request_send => sub ($request, $ua, $handler) {
ok looks_like_number($request->header('X-API-Microtime')), 'microtime set';
is $request->header('X-API-Key'), 'PERCIVALKEY02', 'api key set';
is length($request->header('X-API-Hash')), 40, 'hash set';
return HTTP::Response->new(200); # terminate the processing
});
$ua->get('http://foobar/some/path');
};

done_testing();

0 comments on commit c612650

Please sign in to comment.