blob: acfda368449447630ae22b1d93dfdd403252ba0b [file] [log] [blame]
// Copyright 2017 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 "authpolicy/tgt_manager.h"
#include <algorithm>
#include <vector>
#include <base/files/file_util.h>
#include <base/location.h>
#include <base/strings/stringprintf.h>
#include <base/threading/platform_thread.h>
#include <base/threading/thread_task_runner_handle.h>
#include "authpolicy/anonymizer.h"
#include "authpolicy/authpolicy_flags.h"
#include "authpolicy/authpolicy_metrics.h"
#include "authpolicy/constants.h"
#include "authpolicy/jail_helper.h"
#include "authpolicy/log_colors.h"
#include "authpolicy/platform_helper.h"
#include "authpolicy/process_executor.h"
#include "authpolicy/samba_helper.h"
#include "bindings/authpolicy_containers.pb.h"
namespace authpolicy {
namespace {
// Requested TGT lifetimes in the kinit command. Format is 1d2h3m. If a server
// has a lower maximum lifetimes, the lifetimes of the TGT are capped.
const char kRequestedTgtValidityLifetime[] = "1d";
const char kRequestedTgtRenewalLifetime[] = "7d";
// Don't try to renew TGTs more often than this interval.
const int kMinTgtRenewDelaySeconds = 300;
static_assert(kMinTgtRenewDelaySeconds > 0, "");
// Fraction of the TGT validity lifetime to schedule automatic TGT renewal. For
// instance, if the TGT is valid for another 1000 seconds and the factor is 0.8,
// the TGT would be renewed after 800 seconds. Must be strictly between 0 and 1.
constexpr float kTgtRenewValidityLifetimeFraction = 0.8f;
static_assert(kTgtRenewValidityLifetimeFraction > 0.0f, "");
static_assert(kTgtRenewValidityLifetimeFraction < 1.0f, "");
// Size limit for GetKerberosFiles (1 MB).
const size_t kKrb5FileSizeLimit = 1024 * 1024;
// Invalid/unset file descriptor.
constexpr int kInvalidFd = -1;
// Encryption types for Kerberos configuration
constexpr char kEncTypesAES[] =
"aes256-cts-hmac-sha1-96 aes128-cts-hmac-sha1-96";
constexpr char kEncTypesRC4[] = "rc4-hmac";
// Kerberos configuration file data.
const char kKrb5ConfData[] =
"[libdefaults]\n"
"\tdefault_tgs_enctypes = %s\n"
"\tdefault_tkt_enctypes = %s\n"
"\tpermitted_enctypes = %s\n"
// Prune weak ciphers from the above list. With current settings it’s a
// no-op, but still.
"\tallow_weak_crypto = false\n"
// Default is 300 seconds, but we might add a policy for that in the future.
"\tclockskew = 300\n"
// Required for password change.
"\tdefault_realm = %s\n";
const char kKrb5RealmData[] =
"[realms]\n"
"\t%s = {\n"
"\t\tkdc = [%s]\n"
"\t\tkpasswd_server = [%s]\n"
"\t}\n";
// Env variable to trace debug info of kinit and kpasswd.
const char kKrb5TraceEnvKey[] = "KRB5_TRACE";
// Maximum kinit tries.
const int kKinitMaxTries = 60;
// Wait interval between two kinit tries.
const int kKinitRetryWaitSeconds = 1;
// Keys for interpreting kinit, klist and kpasswd output.
const char kKeyBadPrincipal[] =
"not found in Kerberos database while getting initial credentials";
const char kKeyBadPrincipal2[] =
"Client not found in Kerberos database getting initial ticket";
const char kKeyBadPassword[] =
"Preauthentication failed while getting initial credentials";
const char kKeyBadPassword2[] =
"Password incorrect while getting initial credentials";
const char kKeyBadPassword3[] =
"Preauthentication failed getting initial ticket";
const char kKeyPasswordExpiredStdout[] =
"Password expired. You must change it now.";
const char kKeyPasswordRejectedStdout[] = "Password change rejected";
const char kCannotReadPasswordStderr[] =
"Cannot read password while getting initial credentials";
const char kKeyCannotResolve[] =
"Cannot resolve network address for KDC in realm";
const char kKeyCannotContactKDC[] = "Cannot contact any KDC";
const char kKeyCannotFindKDC[] = "Cannot find KDC";
const char kKeyNoCrentialsCache[] = "No credentials cache found";
const char kKeyTicketExpired[] = "Ticket expired while renewing credentials";
const char kKeyEncTypeNotSupported[] = "KDC has no support for encryption type";
// Nice marker for TGT renewal related logs, for easy grepping.
const char kTgtRenewalHeader[] = "TGT RENEWAL - ";
// Returns true if the given principal is a machine principal.
bool IsMachine(const std::string& principal) {
return Contains(principal, "$@");
}
// Reads the file at |path| into |data|. Returns |ERROR_LOCAL_IO| if the file
// could not be read.
WARN_UNUSED_RESULT ErrorType ReadFile(const base::FilePath& path,
std::string* data) {
data->clear();
if (!base::ReadFileToStringWithMaxSize(path, data, kKrb5FileSizeLimit)) {
PLOG(ERROR) << "Failed to read '" << path.value() << "'";
data->clear();
return ERROR_LOCAL_IO;
}
return ERROR_NONE;
}
// Formats a time delta in 1h 2m 3s format.
std::string FormatTimeDelta(int 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("%s%im", str.size() > 0 ? " " : "", m);
str += base::StringPrintf("%s%is", str.size() > 0 ? " " : "", s);
return str;
}
std::ostream& operator<<(std::ostream& os,
const protos::TgtLifetime& lifetime) {
os << "(valid for " << FormatTimeDelta(lifetime.validity_seconds())
<< ", renewable for " << FormatTimeDelta(lifetime.renewal_seconds())
<< ")";
return os;
}
// In case kinit failed, checks the output and returns appropriate error codes.
WARN_UNUSED_RESULT ErrorType GetKinitError(const ProcessExecutor& kinit_cmd,
bool is_machine_principal) {
DCHECK_NE(0, kinit_cmd.GetExitCode());
const std::string& kinit_out = kinit_cmd.GetStdout();
const std::string& kinit_err = kinit_cmd.GetStderr();
if (Contains(kinit_err, kKeyCannotContactKDC)) {
LOG(ERROR) << "kinit failed - failed to contact KDC";
return ERROR_CONTACTING_KDC_FAILED;
}
if (Contains(kinit_err, kKeyBadPrincipal)) {
LOG(ERROR) << "kinit failed - bad "
<< (is_machine_principal ? "machine" : "user") << " name";
return is_machine_principal ? ERROR_BAD_MACHINE_NAME : ERROR_BAD_USER_NAME;
}
if (Contains(kinit_err, kKeyBadPassword) ||
Contains(kinit_err, kKeyBadPassword2)) {
LOG(ERROR) << "kinit failed - bad password";
return ERROR_BAD_PASSWORD;
}
// Check both stderr and stdout here since any kinit error in the change-
// password-workflow would otherwise be interpreted as 'password expired'.
if (Contains(kinit_out, kKeyPasswordExpiredStdout) &&
Contains(kinit_err, kCannotReadPasswordStderr)) {
if (Contains(kinit_out, kKeyPasswordRejectedStdout)) {
LOG(ERROR) << "kinit failed - password rejected";
return ERROR_PASSWORD_REJECTED;
} else {
LOG(ERROR) << "kinit failed - password expired";
return ERROR_PASSWORD_EXPIRED;
}
}
if (Contains(kinit_err, kKeyCannotResolve)) {
LOG(ERROR) << "kinit failed - cannot resolve KDC realm";
return ERROR_NETWORK_PROBLEM;
}
if (Contains(kinit_err, kKeyNoCrentialsCache)) {
LOG(ERROR) << "kinit failed - no credentials cache found";
return ERROR_NO_CREDENTIALS_CACHE_FOUND;
}
if (Contains(kinit_err, kKeyTicketExpired)) {
LOG(ERROR) << "kinit failed - ticket expired";
return ERROR_KERBEROS_TICKET_EXPIRED;
}
if (Contains(kinit_err, kKeyEncTypeNotSupported)) {
LOG(ERROR) << "kinit failed - KDC does not support encryption type";
return ERROR_KDC_DOES_NOT_SUPPORT_ENCRYPTION_TYPE;
}
LOG(ERROR) << "kinit failed with exit code " << kinit_cmd.GetExitCode();
return ERROR_KINIT_FAILED;
}
// In case klist failed, checks the output and returns appropriate error codes.
WARN_UNUSED_RESULT ErrorType GetKListError(const ProcessExecutor& klist_cmd) {
DCHECK_NE(0, klist_cmd.GetExitCode());
const std::string& klist_out = klist_cmd.GetStdout();
const std::string& klist_err = klist_cmd.GetStderr();
if (Contains(klist_err, kKeyNoCrentialsCache)) {
LOG(ERROR) << "klist failed - no credentials cache found";
return ERROR_NO_CREDENTIALS_CACHE_FOUND;
}
// Test the return value of klist -s. The command returns 1 if the TGT is
// invalid and 0 otherwise. Does not print anything.
const std::vector<std::string>& args = klist_cmd.GetArgs();
if (klist_out.empty() && klist_err.empty() &&
std::find(args.begin(), args.end(), "-s") != args.end()) {
LOG(ERROR) << "klist failed - ticket expired";
return ERROR_KERBEROS_TICKET_EXPIRED;
}
LOG(ERROR) << "klist failed with exit code " << klist_cmd.GetExitCode();
return ERROR_KLIST_FAILED;
}
// In case kpasswd failed, checks the output and returns appropriate error
// codes.
WARN_UNUSED_RESULT ErrorType GetKPasswdError(const ProcessExecutor& kpasswd_cmd,
bool is_machine_principal) {
DCHECK_NE(0, kpasswd_cmd.GetExitCode());
const std::string& kpasswd_err = kpasswd_cmd.GetStderr();
if (Contains(kpasswd_err, kKeyCannotContactKDC) ||
Contains(kpasswd_err, kKeyCannotFindKDC)) {
LOG(ERROR) << "kpasswd failed - failed to contact KDC";
return ERROR_CONTACTING_KDC_FAILED;
}
if (Contains(kpasswd_err, kKeyBadPrincipal2)) {
LOG(ERROR) << "kpasswd failed - bad "
<< (is_machine_principal ? "machine" : "user") << " name";
return is_machine_principal ? ERROR_BAD_MACHINE_NAME : ERROR_BAD_USER_NAME;
}
if (Contains(kpasswd_err, kKeyBadPassword3)) {
LOG(ERROR) << "kpasswd failed - bad password";
return ERROR_BAD_PASSWORD;
}
if (Contains(kpasswd_err, kKeyPasswordRejectedStdout)) {
LOG(ERROR) << "kpasswd failed - password rejected";
return ERROR_PASSWORD_REJECTED;
}
LOG(ERROR) << "kpasswd failed with exit code " << kpasswd_cmd.GetExitCode();
return ERROR_KPASSWD_FAILED;
}
std::string GetEncryptionTypesString(KerberosEncryptionTypes encryption_types) {
switch (encryption_types) {
case ENC_TYPES_ALL:
return base::StringPrintf("%s %s", kEncTypesAES, kEncTypesRC4);
case ENC_TYPES_STRONG:
return kEncTypesAES;
case ENC_TYPES_LEGACY:
return kEncTypesRC4;
}
}
} // namespace
TgtManager::TgtManager(const PathService* path_service,
AuthPolicyMetrics* metrics,
const protos::DebugFlags* flags,
const JailHelper* jail_helper,
Anonymizer* anonymizer,
Delegate* delegate,
Path config_path,
Path credential_cache_path)
: paths_(path_service),
metrics_(metrics),
flags_(flags),
jail_helper_(jail_helper),
anonymizer_(anonymizer),
delegate_(delegate),
config_path_(config_path),
credential_cache_path_(credential_cache_path) {}
TgtManager::~TgtManager() {
// Do a best-effort cleanup.
base::DeleteFile(base::FilePath(paths_->Get(config_path_)),
false /* recursive */);
base::DeleteFile(base::FilePath(paths_->Get(credential_cache_path_)),
false /* recursive */);
// Note that the destuctor of |tgt_renewal_callback_| does not cancel.
tgt_renewal_callback_.Cancel();
}
void TgtManager::SetPrincipal(const std::string& principal) {
principal_ = principal;
is_machine_principal_ = IsMachine(principal);
}
void TgtManager::Reset() {
principal_.clear();
is_machine_principal_ = false;
realm_.clear();
kdc_ip_.clear();
kinit_retry_ = false;
encryption_types_ = ENC_TYPES_STRONG;
EnableTgtAutoRenewal(false);
}
ErrorType TgtManager::AcquireTgtWithPassword(int password_fd) {
return AcquireTgt(password_fd, Path::INVALID);
}
ErrorType TgtManager::AcquireTgtWithKeytab(Path keytab_path) {
return AcquireTgt(kInvalidFd, keytab_path);
}
ErrorType TgtManager::AcquireTgt(int password_fd, Path keytab_path) {
// Either password or keytab.
DCHECK((password_fd != kInvalidFd) ^ (keytab_path != Path::INVALID));
// Make sure we have the info we need.
DCHECK(!principal_.empty());
DCHECK(!realm_.empty());
// Call kinit to get the Kerberos ticket-granting-ticket.
ProcessExecutor kinit_cmd(
{paths_->Get(Path::KINIT), principal_, kValidityLifetimeParam,
kRequestedTgtValidityLifetime, kRenewalLifetimeParam,
kRequestedTgtRenewalLifetime});
if (keytab_path != Path::INVALID) {
kinit_cmd.PushArg(kUseKeytabParam);
kinit_cmd.SetEnv(kKrb5KTEnvKey, kFilePrefix + paths_->Get(keytab_path));
}
ErrorType error = RunKinit(&kinit_cmd, password_fd);
if (error == ERROR_CONTACTING_KDC_FAILED) {
LOG(WARNING) << "Retrying kinit without KDC IP config in the krb5.conf";
kdc_ip_.clear();
error = RunKinit(&kinit_cmd, password_fd);
}
// Don't retry again.
kinit_retry_ = false;
// If it worked, re-trigger the TGT renewal task.
if (error == ERROR_NONE && tgt_autorenewal_enabled_)
UpdateTgtAutoRenewal();
// Trigger signal if files changed.
MaybeTriggerKerberosFilesChanged();
return error;
}
ErrorType TgtManager::GetKerberosFiles(KerberosFiles* files) {
files->clear_krb5cc();
files->clear_krb5conf();
ErrorType error;
std::string krb5cc;
{
// Note: The krb5cc is readable only by authpolicyd-exec.
ScopedSwitchToSavedUid switch_scope;
base::FilePath krb5cc_path(paths_->Get(credential_cache_path_));
if (!base::PathExists(krb5cc_path))
return ERROR_NONE;
error = ReadFile(krb5cc_path, &krb5cc);
if (error != ERROR_NONE)
return error;
}
std::string krb5conf;
base::FilePath krb5conf_path(paths_->Get(config_path_));
error = ReadFile(krb5conf_path, &krb5conf);
if (error != ERROR_NONE)
return error;
files->mutable_krb5cc()->assign(krb5cc.begin(), krb5cc.end());
files->mutable_krb5conf()->assign(krb5conf.begin(), krb5conf.end());
return ERROR_NONE;
}
void TgtManager::SetKerberosFilesChangedCallback(
const base::Closure& callback) {
kerberos_files_changed_ = callback;
}
void TgtManager::EnableTgtAutoRenewal(bool enabled) {
if (tgt_autorenewal_enabled_ != enabled) {
tgt_autorenewal_enabled_ = enabled;
UpdateTgtAutoRenewal();
}
}
ErrorType TgtManager::RenewTgt() {
// kinit -R renews the TGT.
ProcessExecutor kinit_cmd({paths_->Get(Path::KINIT), kRenewParam});
ErrorType error = RunKinit(&kinit_cmd, kInvalidFd);
// No matter if it worked or not, reschedule auto-renewal. We might be offline
// and want to try again later.
UpdateTgtAutoRenewal();
// Trigger signal if files changed.
MaybeTriggerKerberosFilesChanged();
// Let the delegate do its thing.
delegate_->OnTgtRenewed();
return error;
}
ErrorType TgtManager::GetTgtLifetime(protos::TgtLifetime* lifetime) {
// Check local file first before calling klist -s, since that would respond
// ERROR_KERBEROS_TICKET_EXPIRED instead of ERROR_NO_CREDENTIALS_CACHE_FOUND.
if (!base::PathExists(base::FilePath(paths_->Get(credential_cache_path_)))) {
LOG(ERROR) << "GetTgtLifetime failed - no credentials cache found";
return ERROR_NO_CREDENTIALS_CACHE_FOUND;
}
// Call klist -s to find out whether the TGT is still valid.
{
ProcessExecutor klist_cmd({paths_->Get(Path::KLIST), kSetExitStatusParam,
kCredentialCacheParam,
paths_->Get(credential_cache_path_)});
if (!jail_helper_->SetupJailAndRun(&klist_cmd, Path::KLIST_SECCOMP,
TIMER_KLIST)) {
return GetKListError(klist_cmd);
}
}
// Now that we know the TGT is valid, call klist again (without -s) and parse
// the output to get the TGT lifetime.
{
ProcessExecutor klist_cmd({paths_->Get(Path::KLIST), kCredentialCacheParam,
paths_->Get(credential_cache_path_)});
if (!jail_helper_->SetupJailAndRun(&klist_cmd, Path::KLIST_SECCOMP,
TIMER_KLIST)) {
return GetKListError(klist_cmd);
}
// Parse the output to find the lifetime. Enclose in a sandbox for security
// considerations.
ProcessExecutor parse_cmd({paths_->Get(Path::PARSER), kCmdParseTgtLifetime,
SerializeFlags(*flags_)});
parse_cmd.SetInputString(klist_cmd.GetStdout());
if (!jail_helper_->SetupJailAndRun(&parse_cmd, Path::PARSER_SECCOMP,
TIMER_NONE)) {
LOG(ERROR) << "authpolicy_parser parse_tgt_lifetime failed with "
<< "exit code " << parse_cmd.GetExitCode();
return ERROR_PARSE_FAILED;
}
if (!lifetime->ParseFromString(parse_cmd.GetStdout())) {
LOG(ERROR) << "Failed to parse TGT lifetime protobuf from string";
return ERROR_PARSE_FAILED;
}
return ERROR_NONE;
}
}
ErrorType TgtManager::ChangePassword(const std::string& old_password,
const std::string& new_password) {
// Write configuration.
ErrorType error = WriteKrb5Conf();
if (error != ERROR_NONE)
return error;
// Write passwords to pipe.
base::ScopedFD password_fd = WriteStringToPipe(
old_password + "\n" + new_password + "\n" + new_password);
if (!password_fd.is_valid())
return ERROR_LOCAL_IO;
// Setup and run kpasswd command.
DCHECK(!principal_.empty());
ProcessExecutor kpasswd_cmd({paths_->Get(Path::KPASSWD), principal_});
kpasswd_cmd.SetInputFile(password_fd.get());
kpasswd_cmd.SetEnv(kKrb5ConfEnvKey, kFilePrefix + paths_->Get(config_path_));
SetupKrb5Trace(&kpasswd_cmd);
if (!jail_helper_->SetupJailAndRun(&kpasswd_cmd, Path::KPASSWD_SECCOMP,
TIMER_KPASSWD)) {
OutputKrb5Trace();
return GetKPasswdError(kpasswd_cmd, is_machine_principal_);
}
return ERROR_NONE;
}
bool TgtManager::Backup(protos::TgtState* state) {
// Read the TGT first since it can fail.
// Note: The krb5cc is readable only by authpolicyd-exec.
std::string krb5cc;
{
ScopedSwitchToSavedUid switch_scope;
base::FilePath krb5cc_path(paths_->Get(credential_cache_path_));
if (!base::ReadFileToStringWithMaxSize(krb5cc_path, &krb5cc,
kKrb5FileSizeLimit)) {
PLOG(ERROR)
<< "TGT backup failed to read Kerberos credential cache from '"
<< krb5cc_path.value() << "'";
return false;
}
}
// Store data in the state blob.
DCHECK(state);
state->set_realm(realm_);
state->set_kdc_ip(kdc_ip_);
state->set_principal(principal_);
state->set_krb5cc(krb5cc);
return true;
}
bool TgtManager::Restore(const protos::TgtState& state) {
// Verify state.
if (!state.has_realm() || !state.has_kdc_ip() || !state.has_principal() ||
!state.has_krb5cc()) {
LOG(ERROR) << "TGT restore failed, invalid state";
return false;
}
// Write TGT first since it can fail.
// Note: The krb5cc is writeable only by authpolicyd-exec.
{
ScopedSwitchToSavedUid switch_scope;
const base::FilePath krb5cc_path(paths_->Get(credential_cache_path_));
const int size = static_cast<int>(state.krb5cc().size());
if (base::WriteFile(krb5cc_path, state.krb5cc().data(), size) != size) {
PLOG(ERROR) << "TGT restore failed to write Kerberos credential cache to "
<< krb5cc_path.value();
return false;
}
}
realm_ = state.realm();
kdc_ip_ = state.kdc_ip();
SetPrincipal(state.principal());
// Do a best effort restoring the config. It is needed e.g. for
// GetKerberosFiles(). Don't exit here since we'd be in an undefined state.
// Even if this fails here, it'll eventually recover since many instances
// write the config.
ignore_result(WriteKrb5Conf());
// Trigger files changed signal.
kerberos_files_dirty_ = true;
MaybeTriggerKerberosFilesChanged();
return true;
}
ErrorType TgtManager::RunKinit(ProcessExecutor* kinit_cmd,
int password_fd) const {
// Write configuration.
ErrorType error = WriteKrb5Conf();
if (error != ERROR_NONE)
return error;
// Set Kerberos credential cache and configuration file paths.
kinit_cmd->SetEnv(kKrb5CCEnvKey, paths_->Get(credential_cache_path_));
kinit_cmd->SetEnv(kKrb5ConfEnvKey, kFilePrefix + paths_->Get(config_path_));
error = ERROR_NONE;
const int max_tries = (kinit_retry_ ? kKinitMaxTries : 1);
int tries, failed_tries = 0;
for (tries = 1; tries <= max_tries; ++tries) {
// Sleep between subsequent tries (probably a propagation issue).
if (tries > 1 && !kinit_retry_sleep_disabled_for_testing_) {
base::PlatformThread::Sleep(
base::TimeDelta::FromSeconds(kKinitRetryWaitSeconds));
}
SetupKrb5Trace(kinit_cmd);
// Set password as input. Duplicate it in any case since we don't know
// whether we'll have to rerun.
base::ScopedFD password_dup;
if (password_fd != kInvalidFd) {
password_dup = DuplicatePipe(password_fd);
if (!password_dup.is_valid()) {
error = ERROR_LOCAL_IO;
break;
}
kinit_cmd->SetInputFile(password_dup.get());
}
if (jail_helper_->SetupJailAndRun(kinit_cmd, Path::KINIT_SECCOMP,
TIMER_KINIT)) {
error = ERROR_NONE;
break;
}
failed_tries++;
OutputKrb5Trace();
error = GetKinitError(*kinit_cmd, is_machine_principal_);
// If kinit fails because credentials are not propagated yet, these are
// the error types you get.
if (error != ERROR_BAD_USER_NAME && error != ERROR_BAD_MACHINE_NAME &&
error != ERROR_BAD_PASSWORD) {
break;
}
}
metrics_->Report(METRIC_KINIT_FAILED_TRY_COUNT, failed_tries);
// If there was no error, assume that the Kerberos credential cache changed.
if (error == ERROR_NONE)
kerberos_files_dirty_ = true;
return error;
}
ErrorType TgtManager::WriteKrb5Conf() const {
const std::string enc_types = GetEncryptionTypesString(encryption_types_);
std::string data =
base::StringPrintf(kKrb5ConfData, enc_types.c_str(), enc_types.c_str(),
enc_types.c_str(), realm_.c_str());
if (!kdc_ip_.empty()) {
data += base::StringPrintf(kKrb5RealmData, realm_.c_str(), kdc_ip_.c_str(),
kdc_ip_.c_str());
}
const base::FilePath krbconf_path(paths_->Get(config_path_));
// Only set kerberos_files_dirty_ if the config data has actually changed.
// Otherwise, the KerberosFilesChanged signal gets triggered way too often,
// causing the krb5cc in Chrome to reset all the time.
std::string prev_data;
if (!base::ReadFileToStringWithMaxSize(krbconf_path, &prev_data,
kKrb5FileSizeLimit) ||
data != prev_data) {
const int data_size = static_cast<int>(data.size());
if (base::WriteFile(krbconf_path, data.data(), data_size) != data_size) {
LOG(ERROR) << "Failed to write krb5 conf file '" << krbconf_path.value()
<< "'";
return ERROR_LOCAL_IO;
}
kerberos_files_dirty_ = true;
}
return ERROR_NONE;
}
void TgtManager::SetupKrb5Trace(ProcessExecutor* krb5_cmd) const {
if (!flags_->trace_krb5())
return;
const std::string& trace_path = paths_->Get(Path::KRB5_TRACE);
{
// Delete krb5 trace file (must be done as authpolicyd-exec).
ScopedSwitchToSavedUid switch_scope;
if (!base::DeleteFile(base::FilePath(trace_path), false /* recursive */)) {
LOG(WARNING) << "Failed to delete krb5 trace file";
}
}
krb5_cmd->SetEnv(kKrb5TraceEnvKey, trace_path);
}
void TgtManager::OutputKrb5Trace() const {
if (!flags_->trace_krb5())
return;
const std::string& trace_path = paths_->Get(Path::KRB5_TRACE);
std::string trace;
{
// Read krb5 trace file (must be done as authpolicyd-exec).
ScopedSwitchToSavedUid switch_scope;
if (!base::ReadFileToString(base::FilePath(trace_path), &trace))
trace = "<failed to read>";
}
LogLongString(kColorKrb5Trace, "Krb5 trace: ", trace, anonymizer_);
}
void TgtManager::UpdateTgtAutoRenewal() {
// Cancel an existing callback if there is any.
if (!tgt_renewal_callback_.IsCancelled())
tgt_renewal_callback_.Cancel();
if (tgt_autorenewal_enabled_) {
// Find out how long the TGT is valid.
protos::TgtLifetime lifetime;
ErrorType error = GetTgtLifetime(&lifetime);
if (error == ERROR_NONE && lifetime.validity_seconds() > 0) {
if (lifetime.validity_seconds() >= lifetime.renewal_seconds()) {
// If we TGT got renewed a lot and/or is not renewable, the validity
// lifetime is bounded by the renewal lifetime.
LOG(WARNING) << kTgtRenewalHeader << "TGT cannot be renewed anymore "
<< lifetime;
} else {
// Trigger the renewal somewhere in the validity lifetime of the TGT.
int delay_seconds = static_cast<int>(lifetime.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);
LOG(INFO) << kTgtRenewalHeader << "Scheduling renewal in "
<< FormatTimeDelta(delay_seconds) << " " << lifetime;
tgt_renewal_callback_.Reset(
base::Bind(&TgtManager::AutoRenewTgt, base::Unretained(this)));
base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE, tgt_renewal_callback_.callback(),
base::TimeDelta::FromSeconds(delay_seconds));
}
} else if (error == ERROR_KERBEROS_TICKET_EXPIRED) {
// Expiry is the most likely error, print a nice message.
LOG(WARNING) << kTgtRenewalHeader << "TGT expired, reinitializing "
<< "requires credentials";
}
}
}
void TgtManager::AutoRenewTgt() {
LOG(INFO) << kTgtRenewalHeader << "Running scheduled TGT renewal";
ErrorType error = RenewTgt();
if (error == ERROR_NONE)
LOG(INFO) << kTgtRenewalHeader << "Succeeded";
else
LOG(ERROR) << kTgtRenewalHeader << "Failed with error " << error;
metrics_->ReportError(ERROR_OF_AUTO_TGT_RENEWAL, error);
}
void TgtManager::MaybeTriggerKerberosFilesChanged() {
if (kerberos_files_dirty_ && !kerberos_files_changed_.is_null())
kerberos_files_changed_.Run();
kerberos_files_dirty_ = false;
}
} // namespace authpolicy