diff --git a/Cargo.lock b/Cargo.lock index f94fe7882af62..382514bdafa2f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1819,6 +1819,8 @@ version = "0.1.0" dependencies = [ "common-base", "common-exception", + "jwt-simple", + "serde", ] [[package]] @@ -3730,6 +3732,7 @@ dependencies = [ "common-config", "common-exception", "common-license", + "jwt-simple", ] [[package]] diff --git a/src/common/exception/src/exception_code.rs b/src/common/exception/src/exception_code.rs index 8a5a30f163e65..6202aa4bb4fde 100644 --- a/src/common/exception/src/exception_code.rs +++ b/src/common/exception/src/exception_code.rs @@ -159,6 +159,18 @@ build_exceptions! { /// /// For example: try to with 3 columns into a table with 4 columns. TableSchemaMismatch(1303), + + // License related errors starts here + + /// LicenseKeyParseError is used when license key cannot be pared by the jwt public key + /// + /// For example: license key is not valid + LicenseKeyParseError(1401), + + /// LicenseKeyInvalid is used when license key verification error occurs + /// + /// For example: license key is expired + LicenseKeyInvalid(1402) } // Meta service errors [2001, 3000]. diff --git a/src/common/license/Cargo.toml b/src/common/license/Cargo.toml index 461d563412cb3..ebdd588742b1c 100644 --- a/src/common/license/Cargo.toml +++ b/src/common/license/Cargo.toml @@ -14,3 +14,5 @@ test = false # Workspace dependencies common-base = { path = "../base" } common-exception = { path = "../exception" } +jwt-simple = "0.11.0" +serde = { workspace = true } diff --git a/src/common/license/src/lib.rs b/src/common/license/src/lib.rs index 28b84074bb612..d869b91fcecee 100644 --- a/src/common/license/src/lib.rs +++ b/src/common/license/src/lib.rs @@ -12,4 +12,5 @@ // See the License for the specific language governing permissions and // limitations under the License. +pub mod license; pub mod license_manager; diff --git a/src/common/license/src/license.rs b/src/common/license/src/license.rs new file mode 100644 index 0000000000000..82d20a84384e6 --- /dev/null +++ b/src/common/license/src/license.rs @@ -0,0 +1,29 @@ +// Copyright 2021 Datafuse Labs +// +// Licensed 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. + +use serde::Deserialize; +use serde::Serialize; + +pub const LICENSE_PUBLIC_KEY: &str = r#"-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEGsKCbhXU7j56VKZ7piDlLXGhud0a +pWjW3wxSdeARerxs/BeoWK7FspDtfLaAT8iJe4YEmR0JpkRQ8foWs0ve3w== +-----END PUBLIC KEY-----"#; + +#[derive(Serialize, Deserialize)] +pub struct LicenseInfo { + #[serde(rename = "type")] + pub r#type: Option, + pub org: Option, + pub tenants: Option>, +} diff --git a/src/common/license/src/license_manager.rs b/src/common/license/src/license_manager.rs index 898419e5f45cc..36e1728a81726 100644 --- a/src/common/license/src/license_manager.rs +++ b/src/common/license/src/license_manager.rs @@ -16,6 +16,9 @@ use std::sync::Arc; use common_base::base::GlobalInstance; use common_exception::Result; +use jwt_simple::claims::JWTClaims; + +use crate::license::LicenseInfo; pub trait LicenseManager { fn init() -> Result<()> @@ -23,6 +26,33 @@ pub trait LicenseManager { fn instance() -> Arc> where Self: Sized; fn is_active(&self) -> bool; + + /// Encodes a raw license string as a JWT using the constant public key. + /// + /// This function takes a raw license string and a secret key, + /// The function returns a `jwt_simple::Claim` object that represents the + /// decoded contents of the JWT with custom fields `LicenseInfo` + /// + /// # Arguments + /// + /// * `raw` - The raw license string to be encoded. + /// + /// # Returns + /// + /// A `jwt_simple::Claim` object representing the decoded contents of the JWT. + /// + /// # Errors + /// + /// This function may return `LicenseKeyParseError` error if the encoding or decoding of the JWT fails. + /// + /// # Examples + /// + /// ``` + /// let raw_license = "my_license"; + /// let claim = make_license(raw_license).unwrap(); + /// ``` + fn make_license(raw: &str) -> Result> + where Self: Sized; } pub struct LicenseManagerWrapper { diff --git a/src/query/ee/Cargo.toml b/src/query/ee/Cargo.toml index ba4b548ccde04..db2c09838d334 100644 --- a/src/query/ee/Cargo.toml +++ b/src/query/ee/Cargo.toml @@ -13,12 +13,12 @@ test = false [dependencies] # Workspace dependencies +async-backtrace = { workspace = true } common-base = { path = "../../common/base" } common-config = { path = "../config" } common-exception = { path = "../../common/exception" } common-license = { path = "../../common/license" } - -async-backtrace = { workspace = true } +jwt-simple = "0.11.0" [build-dependencies] common-building = { path = "../../common/building" } diff --git a/src/query/ee/src/license/license_mgr.rs b/src/query/ee/src/license/license_mgr.rs index 9ac85624eee03..34ca276e5c84c 100644 --- a/src/query/ee/src/license/license_mgr.rs +++ b/src/query/ee/src/license/license_mgr.rs @@ -15,9 +15,16 @@ use std::sync::Arc; use common_base::base::GlobalInstance; +use common_exception::exception::ErrorCode; use common_exception::Result; +use common_exception::ToErrorCode; +use common_license::license::LicenseInfo; +use common_license::license::LICENSE_PUBLIC_KEY; use common_license::license_manager::LicenseManager; use common_license::license_manager::LicenseManagerWrapper; +use jwt_simple::algorithms::ES256PublicKey; +use jwt_simple::claims::JWTClaims; +use jwt_simple::prelude::ECDSAP256PublicKeyLike; pub struct RealLicenseManager {} @@ -38,4 +45,15 @@ impl LicenseManager for RealLicenseManager { fn is_active(&self) -> bool { true } + + fn make_license(raw: &str) -> Result> { + let public_key = ES256PublicKey::from_pem(LICENSE_PUBLIC_KEY) + .map_err_to_code(ErrorCode::LicenseKeyParseError, || "public key load failed")?; + public_key + .verify_token::(raw, None) + .map_err_to_code( + ErrorCode::LicenseKeyParseError, + || "jwt claim decode failed", + ) + } } diff --git a/src/query/ee/tests/it/license/license_mgr.rs b/src/query/ee/tests/it/license/license_mgr.rs new file mode 100644 index 0000000000000..5335696710dad --- /dev/null +++ b/src/query/ee/tests/it/license/license_mgr.rs @@ -0,0 +1,34 @@ +// Copyright 2023 Databend Cloud +// +// Licensed under the Elastic 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 +// +// https://www.elastic.co/licensing/elastic-license +// +// 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. + +use common_license::license_manager::LicenseManager; +use enterprise_query::license::license_mgr::RealLicenseManager; + +#[test] +fn test_make_license() -> common_exception::Result<()> { + assert_eq!( + RealLicenseManager::make_license("not valid") + .err() + .unwrap() + .code(), + common_exception::ErrorCode::LicenseKeyParseError("").code() + ); + let valid_key = "eyJhbGciOiAiRVMyNTYiLCAidHlwIjogIkpXVCJ9.eyJ0eXBlIjogInRyaWFsIiwgIm9yZyI6ICJ0ZXN0IiwgInRlbmFudHMiOiBudWxsLCAiaXNzIjogImRhdGFiZW5kIiwgImlhdCI6IDE2ODE4OTk1NjIsICJuYmYiOiAxNjgxODk5NTYyLCAiZXhwIjogMTY4MzE5NTU2MX0.EfnbkZgNGuCUM0yZ7wg1ARgkiY3g32OHkoWZYoEADNEBPd4Dp8Dhq-W5qzTTSEthiMiiRym0DswGpzYPFSwQww"; + let claim = RealLicenseManager::make_license(valid_key).unwrap(); + assert_eq!(claim.issuer.unwrap(), "databend"); + assert_eq!(claim.custom.org.unwrap(), "test"); + assert_eq!(claim.custom.r#type.unwrap(), "trial"); + assert!(claim.custom.tenants.is_none()); + Ok(()) +} diff --git a/src/query/ee/tests/it/license/mod.rs b/src/query/ee/tests/it/license/mod.rs new file mode 100644 index 0000000000000..a7502138a9eae --- /dev/null +++ b/src/query/ee/tests/it/license/mod.rs @@ -0,0 +1,15 @@ +// Copyright 2023 Databend Cloud +// +// Licensed under the Elastic 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 +// +// https://www.elastic.co/licensing/elastic-license +// +// 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. + +mod license_mgr; diff --git a/src/query/ee/tests/it/main.rs b/src/query/ee/tests/it/main.rs new file mode 100644 index 0000000000000..60abf3e4777ef --- /dev/null +++ b/src/query/ee/tests/it/main.rs @@ -0,0 +1,16 @@ +// Copyright 2023 Databend Cloud +// +// Licensed under the Elastic 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 +// +// https://www.elastic.co/licensing/elastic-license +// +// 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. + +#![feature(unwrap_infallible)] +mod license;