blob: dfa7b8d4a22c3580abe10471cb1802531dfa7388 [file] [log] [blame]
// Copyright 2019 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "kerberos/tgt_renewal_scheduler.h"
#include <algorithm>
#include <base/files/file_util.h>
#include <base/logging.h>
#include <base/strings/stringprintf.h>
#include <base/threading/thread_task_runner_handle.h>
#include <base/time/time.h>
#include "bindings/kerberos_containers.pb.h"
#include "kerberos/error_strings.h"
#include "kerberos/krb5_interface.h"
namespace kerberos {
namespace {
// Nice marker for TGT renewal related logs, for easy grepping.
constexpr char kLogHeader[] = "TGT RENEWAL - ";
// Don't try to renew TGTs more often than this interval.
constexpr int kMinTgtRenewDelaySeconds = 60;
static_assert(kMinTgtRenewDelaySeconds > 0, "");
// Formats a time delta in 1h 2m 3s format.
std::string FormatTimeDelta(int64_t delta_seconds) {
int h = delta_seconds / 3600;
int m = (delta_seconds / 60) % 60;
int s = delta_seconds % 60;
std::string str;
if (h > 0)
str += base::StringPrintf("%ih ", h);
if (h > 0 || m > 0)
str += base::StringPrintf("%im ", m);
str += base::StringPrintf("%is", s);
return str;
}
std::ostream& operator<<(std::ostream& os,
const Krb5Interface::TgtStatus& tgt_status) {
os << "(valid for " << FormatTimeDelta(tgt_status.validity_seconds)
<< ", renewable for " << FormatTimeDelta(tgt_status.renewal_seconds)
<< ")";
return os;
}
} // namespace
TgtRenewalScheduler::TgtRenewalScheduler(const std::string& principal_name,
Delegate* delegate)
: principal_name_(principal_name), delegate_(delegate) {}
void TgtRenewalScheduler::ScheduleRenewal(bool notify_expiration) {
// Cancel an existing callback if there is any.
if (!tgt_renewal_callback_.IsCancelled())
tgt_renewal_callback_.Cancel();
// If the TGT exists, but it's broken somehow, assume it's invalid.
Krb5Interface::TgtStatus tgt_status;
if (delegate_->GetTgtStatus(principal_name_, &tgt_status) != ERROR_NONE) {
VLOG(1) << kLogHeader << "Failed to get TGT status";
if (notify_expiration)
delegate_->NotifyTgtExpiration(principal_name_, TgtExpiration::kExpired);
return;
}
// Is the TGT expired?
if (tgt_status.validity_seconds <= 0) {
VLOG(1) << kLogHeader << "TGT about to expire or expired";
if (notify_expiration)
delegate_->NotifyTgtExpiration(principal_name_, TgtExpiration::kExpired);
return;
}
// Is the TGT about to expire? At this point we already give up and show a
// notification in Chrome, so the user can relog.
if (tgt_status.validity_seconds <= kExpirationHeadsUpTimeSeconds) {
VLOG(1) << kLogHeader << "TGT about to expire";
if (notify_expiration)
delegate_->NotifyTgtExpiration(principal_name_,
TgtExpiration::kAboutToExpire);
return;
}
// Note: Reschedule even if the ticket is not renewable anymore, i.e.
// if tgt_status.validity_seconds >= tgt_status.renewal_seconds. The account
// manager might have credentials stored for the account, which allows it to
// auto-refresh the ticket without user input. If we didn't reschedule, we'd
// miss the opportunity to auth-refresh the ticket.
// Trigger the renewal somewhere in the validity lifetime of the TGT.
int delay_seconds = static_cast<int>(tgt_status.validity_seconds *
kTgtRenewValidityLifetimeFraction);
// Make sure we don't trigger excessively often in case the renewal
// fails and we're getting close to the end of the validity lifetime.
delay_seconds = std::max(delay_seconds, kMinTgtRenewDelaySeconds);
VLOG(1) << kLogHeader << "Scheduling renewal in "
<< FormatTimeDelta(delay_seconds) << " " << tgt_status;
tgt_renewal_callback_.Reset(base::BindOnce(
&TgtRenewalScheduler::RunScheduledTgtRenewal, base::Unretained(this)));
base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE, tgt_renewal_callback_.callback(),
base::TimeDelta::FromSeconds(delay_seconds));
}
void TgtRenewalScheduler::RunScheduledTgtRenewal() {
VLOG(1) << kLogHeader << "Running scheduled TGT renewal";
ErrorType error = delegate_->RenewTgt(principal_name_);
// No matter if it worked or not, reschedule auto-renewal. We might be offline
// and want to try again later.
ScheduleRenewal(true /* notify_expiration */);
if (error == ERROR_NONE)
VLOG(1) << kLogHeader << "Succeeded";
else
LOG(ERROR) << kLogHeader << "Failed with error " << GetErrorString(error);
}
} // namespace kerberos