mirror of
https://github.com/GothenburgBitFactory/taskwarrior.git
synced 2025-06-26 10:54:26 +02:00
added BYOS (Bring Your Own SERVICE_ACCOUNT) for GCS authentication (#3262)
This commit is contained in:
parent
83bbe4ec37
commit
aeb6acf640
10 changed files with 88 additions and 12 deletions
|
@ -88,6 +88,42 @@ Then configure Taskwarrior with:
|
||||||
|
|
||||||
$ task config sync.gcp.bucket <bucket-name>
|
$ task config sync.gcp.bucket <bucket-name>
|
||||||
|
|
||||||
|
However you can bring your own service account credentials if your
|
||||||
|
`application-default` is already being used by some other application
|
||||||
|
|
||||||
|
To begin, navigate to the "IAM and Admin" section in the Navigation Menu, then select "Roles."
|
||||||
|
|
||||||
|
On the top menu bar within the "Roles" section, click "CREATE ROLE."
|
||||||
|
Provide an appropriate name and description for the new role.
|
||||||
|
|
||||||
|
Add permissions to your new role using the filter "Service:storage" (not the "Filter permissions by role" input box).
|
||||||
|
Select the following permissions:
|
||||||
|
|
||||||
|
- storage.buckets.create
|
||||||
|
- storage.buckets.get
|
||||||
|
- storage.buckets.update
|
||||||
|
- storage.objects.create
|
||||||
|
- storage.objects.get
|
||||||
|
- storage.objects.list
|
||||||
|
- storage.objects.update
|
||||||
|
|
||||||
|
Create your new role.
|
||||||
|
|
||||||
|
On the left sidebar, navigate to "Service accounts."
|
||||||
|
|
||||||
|
On the top menu bar within the "Service accounts" section, click "CREATE SERVICE ACCOUNT."
|
||||||
|
Provide an appropriate name and description for the new service account.
|
||||||
|
Select the role you just created and complete the service account creation process.
|
||||||
|
|
||||||
|
Now, in the Service Account dashboard, click into the new service account and select "keys" on the top menu bar.
|
||||||
|
Click on "ADD KEY" to create and download a new key (a JSON key).
|
||||||
|
|
||||||
|
|
||||||
|
Then configure Taskwarrior with:
|
||||||
|
|
||||||
|
$ task config sync.gcp.bucket <bucket-name>
|
||||||
|
$ task config sync.gcp.credential_path <absolute-path-to-downloaded-credentials>
|
||||||
|
|
||||||
.SS Local Synchronization
|
.SS Local Synchronization
|
||||||
|
|
||||||
In order to take advantage of synchronization's side effect of saving disk
|
In order to take advantage of synchronization's side effect of saving disk
|
||||||
|
|
|
@ -283,6 +283,7 @@ std::string configurationDefaults =
|
||||||
"#sync.server.client_id # Client ID for sync to a server\n"
|
"#sync.server.client_id # Client ID for sync to a server\n"
|
||||||
"#sync.server.origin # Origin of the sync server\n"
|
"#sync.server.origin # Origin of the sync server\n"
|
||||||
"#sync.local.server_dir # Directory for local sync\n"
|
"#sync.local.server_dir # Directory for local sync\n"
|
||||||
|
"#sync.gcp.credential_path # Path to JSON file containing credentials to authenticate GCP Sync\n"
|
||||||
"#sync.gcp.bucket # Bucket for sync to GCP\n"
|
"#sync.gcp.bucket # Bucket for sync to GCP\n"
|
||||||
"\n"
|
"\n"
|
||||||
"# Aliases - alternate names for commands\n"
|
"# Aliases - alternate names for commands\n"
|
||||||
|
|
|
@ -193,6 +193,7 @@ int CmdShow::execute (std::string& output)
|
||||||
" sugar"
|
" sugar"
|
||||||
" summary.all.projects"
|
" summary.all.projects"
|
||||||
" sync.local.server_dir"
|
" sync.local.server_dir"
|
||||||
|
" sync.gcp.credential_path"
|
||||||
" sync.gcp.bucket"
|
" sync.gcp.bucket"
|
||||||
" sync.server.client_id"
|
" sync.server.client_id"
|
||||||
" sync.encryption_secret"
|
" sync.encryption_secret"
|
||||||
|
|
|
@ -64,6 +64,7 @@ int CmdSync::execute (std::string& output)
|
||||||
// If no server is set up, quit.
|
// If no server is set up, quit.
|
||||||
std::string origin = Context::getContext ().config.get ("sync.server.origin");
|
std::string origin = Context::getContext ().config.get ("sync.server.origin");
|
||||||
std::string server_dir = Context::getContext ().config.get ("sync.local.server_dir");
|
std::string server_dir = Context::getContext ().config.get ("sync.local.server_dir");
|
||||||
|
std::string gcp_credential_path = Context::getContext ().config.get ("sync.gcp.credential_path");
|
||||||
std::string gcp_bucket = Context::getContext ().config.get ("sync.gcp.bucket");
|
std::string gcp_bucket = Context::getContext ().config.get ("sync.gcp.bucket");
|
||||||
std::string encryption_secret = Context::getContext ().config.get ("sync.encryption_secret");
|
std::string encryption_secret = Context::getContext ().config.get ("sync.encryption_secret");
|
||||||
if (server_dir != "") {
|
if (server_dir != "") {
|
||||||
|
@ -73,7 +74,7 @@ int CmdSync::execute (std::string& output)
|
||||||
if (encryption_secret == "") {
|
if (encryption_secret == "") {
|
||||||
throw std::string ("sync.encryption_secret is required");
|
throw std::string ("sync.encryption_secret is required");
|
||||||
}
|
}
|
||||||
server = tc::Server::new_gcp (gcp_bucket, encryption_secret);
|
server = tc::Server::new_gcp (gcp_bucket, gcp_credential_path, encryption_secret);
|
||||||
std::ostringstream os;
|
std::ostringstream os;
|
||||||
os << "GCP bucket " << gcp_bucket;
|
os << "GCP bucket " << gcp_bucket;
|
||||||
server_ident = os.str();
|
server_ident = os.str();
|
||||||
|
|
|
@ -79,13 +79,14 @@ tc::Server::new_sync (const std::string &origin, const std::string &client_id, c
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
tc::Server
|
tc::Server
|
||||||
tc::Server::new_gcp (const std::string &bucket, const std::string &encryption_secret)
|
tc::Server::new_gcp (const std::string &bucket, const std::string &credential_path, const std::string &encryption_secret)
|
||||||
{
|
{
|
||||||
TCString tc_bucket = tc_string_borrow (bucket.c_str ());
|
TCString tc_bucket = tc_string_borrow (bucket.c_str ());
|
||||||
TCString tc_encryption_secret = tc_string_borrow (encryption_secret.c_str ());
|
TCString tc_encryption_secret = tc_string_borrow (encryption_secret.c_str ());
|
||||||
|
TCString tc_credential_path = tc_string_borrow (credential_path.c_str ());
|
||||||
|
|
||||||
TCString error;
|
TCString error;
|
||||||
auto tcserver = tc_server_new_gcp (tc_bucket, tc_encryption_secret, &error);
|
auto tcserver = tc_server_new_gcp (tc_bucket, tc_credential_path, tc_encryption_secret, &error);
|
||||||
if (!tcserver) {
|
if (!tcserver) {
|
||||||
auto errmsg = format ("Could not configure connection to GCP bucket {1}: {2}",
|
auto errmsg = format ("Could not configure connection to GCP bucket {1}: {2}",
|
||||||
bucket, tc_string_content (&error));
|
bucket, tc_string_content (&error));
|
||||||
|
|
|
@ -57,7 +57,7 @@ namespace tc {
|
||||||
static Server new_sync (const std::string &origin, const std::string &client_id, const std::string &encryption_secret);
|
static Server new_sync (const std::string &origin, const std::string &client_id, const std::string &encryption_secret);
|
||||||
|
|
||||||
// Construct a GCP server (tc_server_new_gcp).
|
// Construct a GCP server (tc_server_new_gcp).
|
||||||
static Server new_gcp (const std::string &bucket, const std::string &encryption_secret);
|
static Server new_gcp (const std::string &bucket, const std::string &credential_path, const std::string &encryption_secret);
|
||||||
|
|
||||||
// This object "owns" inner, so copy is not allowed.
|
// This object "owns" inner, so copy is not allowed.
|
||||||
Server (const Server &) = delete;
|
Server (const Server &) = delete;
|
||||||
|
|
|
@ -164,12 +164,14 @@ pub unsafe extern "C" fn tc_server_new_sync(
|
||||||
///
|
///
|
||||||
/// ```c
|
/// ```c
|
||||||
/// EXTERN_C struct TCServer *tc_server_new_gcp(struct TCString bucket,
|
/// EXTERN_C struct TCServer *tc_server_new_gcp(struct TCString bucket,
|
||||||
|
/// struct TCString credential_path,
|
||||||
/// struct TCString encryption_secret,
|
/// struct TCString encryption_secret,
|
||||||
/// struct TCString *error_out);
|
/// struct TCString *error_out);
|
||||||
/// ```
|
/// ```
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub unsafe extern "C" fn tc_server_new_gcp(
|
pub unsafe extern "C" fn tc_server_new_gcp(
|
||||||
bucket: TCString,
|
bucket: TCString,
|
||||||
|
credential_path_argument: TCString,
|
||||||
encryption_secret: TCString,
|
encryption_secret: TCString,
|
||||||
error_out: *mut TCString,
|
error_out: *mut TCString,
|
||||||
) -> *mut TCServer {
|
) -> *mut TCServer {
|
||||||
|
@ -180,15 +182,27 @@ pub unsafe extern "C" fn tc_server_new_gcp(
|
||||||
// - bucket ownership is transferred to this function
|
// - bucket ownership is transferred to this function
|
||||||
let bucket = unsafe { TCString::val_from_arg(bucket) }.into_string()?;
|
let bucket = unsafe { TCString::val_from_arg(bucket) }.into_string()?;
|
||||||
|
|
||||||
|
// SAFETY:
|
||||||
|
// - credential_path is valid (promised by caller)
|
||||||
|
// - credential_path ownership is transferred to this function
|
||||||
|
|
||||||
|
let credential_path =
|
||||||
|
unsafe { TCString::val_from_arg(credential_path_argument) }.into_string()?;
|
||||||
|
let credential_path = if credential_path.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(credential_path)
|
||||||
|
};
|
||||||
|
|
||||||
// SAFETY:
|
// SAFETY:
|
||||||
// - encryption_secret is valid (promised by caller)
|
// - encryption_secret is valid (promised by caller)
|
||||||
// - encryption_secret ownership is transferred to this function
|
// - encryption_secret ownership is transferred to this function
|
||||||
let encryption_secret = unsafe { TCString::val_from_arg(encryption_secret) }
|
let encryption_secret = unsafe { TCString::val_from_arg(encryption_secret) }
|
||||||
.as_bytes()
|
.as_bytes()
|
||||||
.to_vec();
|
.to_vec();
|
||||||
|
|
||||||
let server_config = ServerConfig::Gcp {
|
let server_config = ServerConfig::Gcp {
|
||||||
bucket,
|
bucket,
|
||||||
|
credential_path,
|
||||||
encryption_secret,
|
encryption_secret,
|
||||||
};
|
};
|
||||||
let server = server_config.into_server()?;
|
let server = server_config.into_server()?;
|
||||||
|
|
|
@ -446,6 +446,7 @@ EXTERN_C struct TCServer *tc_server_new_sync(struct TCString origin,
|
||||||
//
|
//
|
||||||
// The server must be freed after it is used - tc_replica_sync does not automatically free it.
|
// The server must be freed after it is used - tc_replica_sync does not automatically free it.
|
||||||
EXTERN_C struct TCServer *tc_server_new_gcp(struct TCString bucket,
|
EXTERN_C struct TCServer *tc_server_new_gcp(struct TCString bucket,
|
||||||
|
struct TCString credential_path,
|
||||||
struct TCString encryption_secret,
|
struct TCString encryption_secret,
|
||||||
struct TCString *error_out);
|
struct TCString *error_out);
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
use super::service::{ObjectInfo, Service};
|
use super::service::{ObjectInfo, Service};
|
||||||
use crate::errors::Result;
|
use crate::errors::Result;
|
||||||
|
use google_cloud_storage::client::google_cloud_auth::credentials::CredentialsFile;
|
||||||
use google_cloud_storage::client::{Client, ClientConfig};
|
use google_cloud_storage::client::{Client, ClientConfig};
|
||||||
use google_cloud_storage::http::error::ErrorResponse;
|
use google_cloud_storage::http::error::ErrorResponse;
|
||||||
use google_cloud_storage::http::Error as GcsError;
|
use google_cloud_storage::http::Error as GcsError;
|
||||||
|
@ -25,9 +26,17 @@ fn is_http_error<T>(query: u16, res: &std::result::Result<T, http::Error>) -> bo
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GcpService {
|
impl GcpService {
|
||||||
pub(in crate::server) fn new(bucket: String) -> Result<Self> {
|
pub(in crate::server) fn new(bucket: String, credential_path: Option<String>) -> Result<Self> {
|
||||||
let rt = Runtime::new()?;
|
let rt = Runtime::new()?;
|
||||||
let config = rt.block_on(ClientConfig::default().with_auth())?;
|
|
||||||
|
let credentialpathstring = credential_path.clone().unwrap();
|
||||||
|
let config: ClientConfig = if credential_path.unwrap() == "" {
|
||||||
|
rt.block_on(ClientConfig::default().with_auth())?
|
||||||
|
} else {
|
||||||
|
let credentials = rt.block_on(CredentialsFile::new_from_file(credentialpathstring))?;
|
||||||
|
rt.block_on(ClientConfig::default().with_credentials(credentials))?
|
||||||
|
};
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
client: Client::new(config),
|
client: Client::new(config),
|
||||||
rt,
|
rt,
|
||||||
|
@ -244,10 +253,16 @@ mod tests {
|
||||||
let Ok(bucket) = std::env::var("GCP_TEST_BUCKET") else {
|
let Ok(bucket) = std::env::var("GCP_TEST_BUCKET") else {
|
||||||
return None;
|
return None;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let Ok(credential_path) = std::env::var("GCP_TEST_CREDENTIAL_PATH") else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
|
||||||
let prefix = Uuid::new_v4();
|
let prefix = Uuid::new_v4();
|
||||||
Some((GcpService::new(bucket).unwrap(), move |n: &_| {
|
Some((
|
||||||
format!("{}-{}", prefix.as_simple(), n).into_bytes()
|
GcpService::new(bucket, Some(credential_path)).unwrap(),
|
||||||
}))
|
move |n: &_| format!("{}-{}", prefix.as_simple(), n).into_bytes(),
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
@ -37,7 +37,12 @@ pub enum ServerConfig {
|
||||||
/// Bucket in which to store the task data. This bucket must not be used for any other
|
/// Bucket in which to store the task data. This bucket must not be used for any other
|
||||||
/// purpose.
|
/// purpose.
|
||||||
bucket: String,
|
bucket: String,
|
||||||
|
/// Path to a GCP credential file, in JSON format. This is required for GCP access incase
|
||||||
|
/// some other application already makes use of Application Default Credentials.
|
||||||
|
/// See https://cloud.google.com/docs/authentication#service-accounts for more details.
|
||||||
|
/// See https://cloud.google.com/iam/docs/keys-create-delete for instructions on how to
|
||||||
|
/// create a service account key.
|
||||||
|
credential_path: Option<String>,
|
||||||
/// Private encryption secret used to encrypt all data sent to the server. This can
|
/// Private encryption secret used to encrypt all data sent to the server. This can
|
||||||
/// be any suitably un-guessable string of bytes.
|
/// be any suitably un-guessable string of bytes.
|
||||||
encryption_secret: Vec<u8>,
|
encryption_secret: Vec<u8>,
|
||||||
|
@ -58,9 +63,10 @@ impl ServerConfig {
|
||||||
#[cfg(feature = "server-gcp")]
|
#[cfg(feature = "server-gcp")]
|
||||||
ServerConfig::Gcp {
|
ServerConfig::Gcp {
|
||||||
bucket,
|
bucket,
|
||||||
|
credential_path,
|
||||||
encryption_secret,
|
encryption_secret,
|
||||||
} => Box::new(CloudServer::new(
|
} => Box::new(CloudServer::new(
|
||||||
GcpService::new(bucket)?,
|
GcpService::new(bucket, credential_path)?,
|
||||||
encryption_secret,
|
encryption_secret,
|
||||||
)?),
|
)?),
|
||||||
})
|
})
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue