I am creating an open source tool called google-play-publisher developed as a .NET 7 REST API which delegates on a Google API .NET Client.
The idea is to run it as a container (i.e: service) in my GitLab CI/CD pipeline in order to upload Android apps (i.e: aab files) to Google Play Developer, as it exposes a simple endpoint that I can invoke with curl
like this:
curl -XPOST --data-binary @your_file.aab http://localhost:5000/api/apps/<your_package_name>/tracks/<track_name>
to make the continuous delivery convenient.
I have all necessary to make it work, including a Google Service Account with proper roles and credentials, which is known by my Google Play Console.
I download the keys as a JSON format, and it has a format like this (fake values):
{
"type": "service_account",
"project_id": "pc-api-...",
"private_key_id": "5342dce..",
"private_key": "-----BEGIN PRIVATE KEY-----\nbunchofcharacters\nandmorecharactes=\n-----END PRIVATE KEY-----\n",
"client_email": "publisher@pc-api-foo-iam.gserviceaccount.com",
"client_id": "123456",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/publisher@pc-api-foo-iam.gserviceaccount.com"
}
NOTE: The -----BEGIN PRIVATE KEY-----
and -----END PRIVATE KEY-----\n
are very necessary. The Google API .NET client library fails if removed.
From that, I just need 6 things:
type
project_id
private_key_id
private_key
client_email
client_id
in order to be authorized by the Google Publisher v3 API and upload AAB for a specific package into a specific track.
The application needs only those 6 settings to be injected when starting, either by configuring them at appsettings.json
or as environment variables injeted when spinning up the container. Pretty standard so far.
My appsettings.json
would look like this (with the proper values from the credentials json)
"ServiceAccount": {
"PrivateKey": "***",
"ClientEmail": "***",
"ProjectId": "***",
"Type": "***",
"PrivateKeyId": "***",
"ClientId": "***"
},
When I configure this appsettings.json
with the proper values, everything works well.
But I don't want to use appsettings.json
for secrets. I need to use environment variables so that I can use the tool with docker and inject the variables when spinning up the container/service.
But when running the tool as a docker container or as a service inside GitLab CI/CD pipeline, I have some problems.
When I run the tool (version 0.1.8) as a docker container like this
docker run \
--name google-play-publisher \
-p 5000:80 \
-e ServiceAccount__PrivateKey="-----BEGIN PRIVATE KEY-----\nbunchofcharacters\nandmorecharactes=\n-----END PRIVATE KEY-----\n" \
-e ServiceAccount__ClientEmail="publisher@pc-api-foo-iam.gserviceaccount.com" \
-e ServiceAccount__ProjectId="pc-api-..."\
-e ServiceAccount__Type="service_account" \
-e ServiceAccount__PrivateKeyId="5342dce.." \
-e ServiceAccount__ClientId="123456" \
registry.gitlab.com/roundev/devops/google-play-publisher:0.1.8
and then I POST an aab file for a package
curl -XPOST --data-binary @my_file.aab http://localhost:5000/api/apps/com.foo.app/tracks/production
The authentication fails
System.ArgumentException: PKCS8 data must be contained within '-----BEGIN PRIVATE KEY-----' and '-----END PRIVATE KEY-----'. (Parameter 'pkcs8PrivateKey')
because docker run
doesn't seem to like passing special environment variables with \n
characters, etc.
It cannot mask it, which is a problem, and then something doesn't work but I cannot see yet the error (no error shown, the pipeline passes but the AAB is not uploaded, so I need to figure out a way to read GitLab CI/CD service logs, which is out of scope for this)
I thought of encoding the private key in base64 (not sure if there is another alternative) in order to gain the following advantages:
But this shows a different problem.
Using an online base 64 encoder and decoding it in the code to generate google auth credentials doesn't work. The \n
characters seem to mess up things.
I have tried different variants, but I always get some invalid_token
problems later on when using Google Publisher API.
// Functions used for encoding and decoding
string EncodeBase64(string text)
{
var textBytes = Encoding.UTF8.GetBytes(text);
var result = Convert.ToBase64String(textBytes);
return result;
}
string DecodeBase64(string base64Text)
{
var base64EncodedBytes = Convert.FromBase64String(base64Text);
var result = Encoding.UTF8.GetString(base64EncodedBytes);
return result;
}
I haven't yet found a good solution that allows me to:
appsetting.json
There must be a good solution using some kind of encoding/decoding private key. But how?
I've resolved it with Hex approach.
Basically the solutions is to grab the Json value for the private_key
as it is, and transform it to Hex, for example using this online tool https://codebeautify.org/string-hex-converter so the private key that looked like this "-----BEGIN PRIVATE KEY-----\nfoo...\nbar\n-----END PRIVATE KEY-----\n"
now looks like this 2d2d2d2d2d424...b45592d2d2d2d2d5c6e
This string has the following characteristics:
private_key
with this:private class JsonCredentials
{
public string Type { get; init; } = string.Empty;
public string PrivateKeyId { get; init; } = string.Empty;
public string PrivateKeyHex { get; init; } = string.Empty;
public string PrivateKey => ToJsonValue(PrivateKeyHex);
public string ClientEmail { get; init; } = string.Empty;
public string ProjectId { get; init; } = string.Empty;
public string ClientId { get; init; } = string.Empty;
private string ToJsonValue(string hexadecimalValue)
{
var hexadecimalValueBytes = Convert.FromHexString(hexadecimalValue);
var result = Encoding.UTF8.GetString(hexadecimalValueBytes);
var unescapedResult = Regex.Unescape(result);
return unescapedResult;
}
}
See my open source repo for more details: https://gitlab.com/roundev/devops/google-play-publisher and to give it a try.