blob: aaf1be80c769d3e45709efcef7e2bb31954e4cfc [file] [log] [blame]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
#include "kudu/security/token_signer.h"
#include <algorithm>
#include <cstdint>
#include <map>
#include <memory>
#include <mutex>
#include <string>
#include <utility>
#include <vector>
#include <gflags/gflags.h>
#include <glog/logging.h>
#include "kudu/gutil/strings/substitute.h"
#include "kudu/gutil/walltime.h"
#include "kudu/security/crypto.h"
#include "kudu/security/token.pb.h"
#include "kudu/security/token_signing_key.h"
#include "kudu/security/token_verifier.h"
#include "kudu/util/flag_tags.h"
#include "kudu/util/locks.h"
#include "kudu/util/status.h"
DEFINE_int32(tsk_num_rsa_bits, 2048,
"Number of bits in RSA keys used for token signing.");
TAG_FLAG(tsk_num_rsa_bits, experimental);
using std::lock_guard;
using std::map;
using std::shared_ptr;
using std::string;
using std::unique_lock;
using std::unique_ptr;
using std::vector;
using strings::Substitute;
namespace kudu {
namespace security {
TokenSigner::TokenSigner(int64_t authn_token_validity_seconds,
int64_t authz_token_validity_seconds,
int64_t key_rotation_seconds,
shared_ptr<TokenVerifier> verifier)
: verifier_(verifier ? std::move(verifier)
: std::make_shared<TokenVerifier>()),
authn_token_validity_seconds_(authn_token_validity_seconds),
authz_token_validity_seconds_(authz_token_validity_seconds),
key_rotation_seconds_(key_rotation_seconds),
// The TSK propagation interval is equal to the rotation interval.
key_validity_seconds_(2 * key_rotation_seconds_ +
std::max(authn_token_validity_seconds_, authz_token_validity_seconds)),
last_key_seq_num_(-1) {
CHECK_GE(key_rotation_seconds_, 0);
CHECK_GE(authn_token_validity_seconds_, 0);
CHECK_GE(authz_token_validity_seconds_, 0);
CHECK(verifier_);
}
TokenSigner::~TokenSigner() {
}
Status TokenSigner::ImportKeys(const vector<TokenSigningPrivateKeyPB>& keys) {
lock_guard<RWMutex> l(lock_);
const int64_t now = WallTime_Now();
map<int64_t, unique_ptr<TokenSigningPrivateKey>> tsk_by_seq;
vector<TokenSigningPublicKeyPB> public_keys_pb;
public_keys_pb.reserve(keys.size());
for (const auto& key : keys) {
// Check the input for consistency.
CHECK(key.has_key_seq_num());
CHECK(key.has_expire_unix_epoch_seconds());
CHECK(key.has_rsa_key_der());
const int64_t key_seq_num = key.key_seq_num();
unique_ptr<TokenSigningPrivateKey> tsk(new TokenSigningPrivateKey(key));
// Advance the key sequence number, if needed. For the use case when the
// history of keys sequence numbers is important, the generated keys are
// persisted when TokenSigner is active and then the keys are imported from
// the store when TokenSigner is initialized (e.g., on restart). It's
// crucial to take into account sequence numbers of all previously persisted
// keys even if they have expired at the moment of importing.
last_key_seq_num_ = std::max(last_key_seq_num_, key_seq_num);
const int64_t key_expire_time = tsk->expire_time();
if (key_expire_time <= now) {
// Do nothing else with an expired TSK.
continue;
}
// Need the public part of the key for the TokenVerifier.
{
TokenSigningPublicKeyPB public_key_pb;
tsk->ExportPublicKeyPB(&public_key_pb);
public_keys_pb.emplace_back(std::move(public_key_pb));
}
tsk_by_seq[key_seq_num] = std::move(tsk);
if (tsk_by_seq.size() > 2) {
tsk_by_seq.erase(tsk_by_seq.begin());
}
}
// Register the public parts of the imported keys with the TokenVerifier.
RETURN_NOT_OK(verifier_->ImportKeys(public_keys_pb));
// Use two most recent keys known so far (in terms of sequence numbers)
// for token signing.
for (auto& e : tsk_deque_) {
const int64_t seq_num = e->key_seq_num();
tsk_by_seq[seq_num] = std::move(e);
}
tsk_deque_.clear();
for (auto& e : tsk_by_seq) {
tsk_deque_.emplace_back(std::move(e.second));
}
while (tsk_deque_.size() > 2) {
tsk_deque_.pop_front();
}
return Status::OK();
}
Status TokenSigner::GenerateAuthzToken(string username,
TablePrivilegePB privilege,
SignedTokenPB* signed_token) const {
if (username.empty()) {
return Status::InvalidArgument("no username provided for authz token");
}
TokenPB token;
token.set_expire_unix_epoch_seconds(WallTime_Now() + authz_token_validity_seconds_);
AuthzTokenPB* authz = token.mutable_authz();
authz->set_username(std::move(username));
*authz->mutable_table_privilege() = std::move(privilege);
SignedTokenPB ret;
if (!token.SerializeToString(ret.mutable_token_data())) {
return Status::RuntimeError("could not serialize authz token");
}
RETURN_NOT_OK(SignToken(&ret));
*signed_token = std::move(ret);
return Status::OK();
}
Status TokenSigner::GenerateAuthnToken(string username,
SignedTokenPB* signed_token) const {
if (username.empty()) {
return Status::InvalidArgument("no username provided for authn token");
}
TokenPB token;
token.set_expire_unix_epoch_seconds(
WallTime_Now() + authn_token_validity_seconds_);
AuthnTokenPB* authn = token.mutable_authn();
authn->mutable_username()->assign(std::move(username));
SignedTokenPB ret;
if (!token.SerializeToString(ret.mutable_token_data())) {
return Status::RuntimeError("could not serialize authn token");
}
RETURN_NOT_OK(SignToken(&ret));
signed_token->Swap(&ret);
return Status::OK();
}
Status TokenSigner::SignToken(SignedTokenPB* token) const {
CHECK(token);
shared_lock<RWMutex> l(lock_);
if (tsk_deque_.empty()) {
return Status::IllegalState("no token signing key");
}
const TokenSigningPrivateKey* key = tsk_deque_.front().get();
RETURN_NOT_OK_PREPEND(key->Sign(token), "could not sign token");
return Status::OK();
}
bool TokenSigner::IsCurrentKeyValid() const {
shared_lock<RWMutex> l(lock_);
if (tsk_deque_.empty()) {
return false;
}
return (tsk_deque_.front()->expire_time() > WallTime_Now());
}
Status TokenSigner::CheckNeedKey(unique_ptr<TokenSigningPrivateKey>* tsk) const {
CHECK(tsk);
const int64_t now = WallTime_Now();
unique_lock<RWMutex> l(lock_);
if (tsk_deque_.empty()) {
// No active key: need a new one.
const int64_t key_seq_num = last_key_seq_num_ + 1;
const int64_t key_expiration = now + key_validity_seconds_;
// Generation of cryptographically strong key takes many CPU cycles;
// do not want to block other parallel activity.
l.unlock();
return GenerateSigningKey(key_seq_num, key_expiration, tsk);
}
if (tsk_deque_.size() >= 2) {
// It does not make much sense to keep more than two keys in the queue.
// It's enough to have just one active key and next key ready to be
// activated when it's time to do so. However, it does not mean the
// process of key refreshment is about to stop once there are two keys
// in the queue: the TryRotateKey() method (which should be called
// periodically along with CheckNeedKey()/AddKey() pair) will eventually
// pop the current key out of the keys queue once the key enters its
// inactive phase.
tsk->reset();
return Status::OK();
}
// The currently active key is in the front of the queue.
const auto* key = tsk_deque_.front().get();
// Check if it's time to generate a new token signing key.
//
// <-----AAAAA===========>
// ^
// now
//
const auto key_creation_time = key->expire_time() - key_validity_seconds_;
if (key_creation_time + key_rotation_seconds_ <= now) {
// It's time to create and start propagating next key.
const int64_t key_seq_num = last_key_seq_num_ + 1;
const int64_t key_expiration = now + key_validity_seconds_;
// Generation of cryptographically strong key takes many CPU cycles:
// do not want to block other parallel activity.
l.unlock();
return GenerateSigningKey(key_seq_num, key_expiration, tsk);
}
// It's not yet time to generate a new key.
tsk->reset();
return Status::OK();
}
Status TokenSigner::AddKey(unique_ptr<TokenSigningPrivateKey> tsk) {
CHECK(tsk);
const int64_t key_seq_num = tsk->key_seq_num();
if (tsk->expire_time() <= WallTime_Now()) {
return Status::InvalidArgument("key has already expired");
}
lock_guard<RWMutex> l(lock_);
if (key_seq_num < last_key_seq_num_ + 1) {
// The AddKey() method is designed for adding new keys: that should be done
// using CheckNeedKey()/AddKey() sequence. Use the ImportKeys() method
// for importing keys in bulk.
return Status::InvalidArgument(
Substitute("$0: invalid key sequence number, should be at least $1",
key_seq_num, last_key_seq_num_ + 1));
}
last_key_seq_num_ = std::max(last_key_seq_num_, key_seq_num);
// Register the public part of the key in TokenVerifier first.
TokenSigningPublicKeyPB public_key_pb;
tsk->ExportPublicKeyPB(&public_key_pb);
RETURN_NOT_OK(verifier_->ImportKeys({public_key_pb}));
tsk_deque_.emplace_back(std::move(tsk));
return Status::OK();
}
Status TokenSigner::TryRotateKey(bool* has_rotated) {
lock_guard<RWMutex> l(lock_);
if (has_rotated) {
*has_rotated = false;
}
if (tsk_deque_.size() < 2) {
// There isn't next key to rotate to.
return Status::OK();
}
const auto* key = tsk_deque_.front().get();
// Check if it's time to switch to next key. The key propagation interval
// is equal to the key rotation interval.
//
// current active key <-----AAAAA===========>
// next key <-----AAAAA===========>
// ^
// now
//
const auto key_creation_time = key->expire_time() - key_validity_seconds_;
if (key_creation_time + 2 * key_rotation_seconds_ <= WallTime_Now()) {
tsk_deque_.pop_front();
if (has_rotated) {
*has_rotated = true;
}
}
return Status::OK();
}
Status TokenSigner::GenerateSigningKey(int64_t key_seq_num,
int64_t key_expiration,
unique_ptr<TokenSigningPrivateKey>* tsk) {
unique_ptr<PrivateKey> key(new PrivateKey());
RETURN_NOT_OK_PREPEND(
GeneratePrivateKey(FLAGS_tsk_num_rsa_bits, key.get()),
"could not generate new RSA token-signing key");
tsk->reset(new TokenSigningPrivateKey(key_seq_num,
key_expiration,
std::move(key)));
return Status::OK();
}
} // namespace security
} // namespace kudu