added BYOS (Bring Your Own SERVICE_ACCOUNT) for GCS authentication (#3262)

This commit is contained in:
Akash Shanmugaraj 2024-01-27 18:27:12 +05:30 committed by GitHub
parent 83bbe4ec37
commit aeb6acf640
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 88 additions and 12 deletions

View file

@ -88,6 +88,42 @@ Then configure Taskwarrior with:
$ 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
In order to take advantage of synchronization's side effect of saving disk

View file

@ -283,6 +283,7 @@ std::string configurationDefaults =
"#sync.server.client_id # Client ID for sync to a server\n"
"#sync.server.origin # Origin of the sync server\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"
"\n"
"# Aliases - alternate names for commands\n"

View file

@ -193,6 +193,7 @@ int CmdShow::execute (std::string& output)
" sugar"
" summary.all.projects"
" sync.local.server_dir"
" sync.gcp.credential_path"
" sync.gcp.bucket"
" sync.server.client_id"
" sync.encryption_secret"

View file

@ -64,6 +64,7 @@ int CmdSync::execute (std::string& output)
// If no server is set up, quit.
std::string origin = Context::getContext ().config.get ("sync.server.origin");
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 encryption_secret = Context::getContext ().config.get ("sync.encryption_secret");
if (server_dir != "") {
@ -73,7 +74,7 @@ int CmdSync::execute (std::string& output)
if (encryption_secret == "") {
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;
os << "GCP bucket " << gcp_bucket;
server_ident = os.str();

View file

@ -79,13 +79,14 @@ tc::Server::new_sync (const std::string &origin, const std::string &client_id, c
////////////////////////////////////////////////////////////////////////////////
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_encryption_secret = tc_string_borrow (encryption_secret.c_str ());
TCString tc_credential_path = tc_string_borrow (credential_path.c_str ());
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) {
auto errmsg = format ("Could not configure connection to GCP bucket {1}: {2}",
bucket, tc_string_content (&error));

View file

@ -57,7 +57,7 @@ namespace tc {
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).
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.
Server (const Server &) = delete;

View file

@ -164,12 +164,14 @@ pub unsafe extern "C" fn tc_server_new_sync(
///
/// ```c
/// EXTERN_C struct TCServer *tc_server_new_gcp(struct TCString bucket,
/// struct TCString credential_path,
/// struct TCString encryption_secret,
/// struct TCString *error_out);
/// ```
#[no_mangle]
pub unsafe extern "C" fn tc_server_new_gcp(
bucket: TCString,
credential_path_argument: TCString,
encryption_secret: TCString,
error_out: *mut TCString,
) -> *mut TCServer {
@ -180,15 +182,27 @@ pub unsafe extern "C" fn tc_server_new_gcp(
// - bucket ownership is transferred to this function
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:
// - encryption_secret is valid (promised by caller)
// - encryption_secret ownership is transferred to this function
let encryption_secret = unsafe { TCString::val_from_arg(encryption_secret) }
.as_bytes()
.to_vec();
let server_config = ServerConfig::Gcp {
bucket,
credential_path,
encryption_secret,
};
let server = server_config.into_server()?;

View file

@ -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.
EXTERN_C struct TCServer *tc_server_new_gcp(struct TCString bucket,
struct TCString credential_path,
struct TCString encryption_secret,
struct TCString *error_out);

View file

@ -1,5 +1,6 @@
use super::service::{ObjectInfo, Service};
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::http::error::ErrorResponse;
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 {
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 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 {
client: Client::new(config),
rt,
@ -244,10 +253,16 @@ mod tests {
let Ok(bucket) = std::env::var("GCP_TEST_BUCKET") else {
return None;
};
let Ok(credential_path) = std::env::var("GCP_TEST_CREDENTIAL_PATH") else {
return None;
};
let prefix = Uuid::new_v4();
Some((GcpService::new(bucket).unwrap(), move |n: &_| {
format!("{}-{}", prefix.as_simple(), n).into_bytes()
}))
Some((
GcpService::new(bucket, Some(credential_path)).unwrap(),
move |n: &_| format!("{}-{}", prefix.as_simple(), n).into_bytes(),
))
}
#[test]

View file

@ -37,7 +37,12 @@ pub enum ServerConfig {
/// Bucket in which to store the task data. This bucket must not be used for any other
/// purpose.
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
/// be any suitably un-guessable string of bytes.
encryption_secret: Vec<u8>,
@ -58,9 +63,10 @@ impl ServerConfig {
#[cfg(feature = "server-gcp")]
ServerConfig::Gcp {
bucket,
credential_path,
encryption_secret,
} => Box::new(CloudServer::new(
GcpService::new(bucket)?,
GcpService::new(bucket, credential_path)?,
encryption_secret,
)?),
})