| // 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 |