37

Dear stackoverflow community, once more I turn to you :)

I've recently come across the wonder of Gitlab and their very nice bundled CI/CD solution. It works gallantly however, we all need to sign our binaries don't we and I've found no way to upload a key as I would to a Jenkins server for doing this.

So, how can I, without checking in my keys and secrets sign my android (actually flutter) application when building a release?

From what I see, most people define the build job with signing settings referring to a non-committed key.properties file specifying a local keystore.jks. This works fine when building APKs locally but if I would like to build and archive them as a part of the CI/CD job, how do I?

For secret keys, for example the passwords to the keystore itself, I've found that I can simply store them as protected variables but the actual keystore file itself. What can I do about that?

Any ideas, suggestions are dearly welcome. Cheers

Edit: I apologise for never marking a right answer here and as @IvanP proposed the solution of writing individual values to a file was what I used for a long time. But as @VonC added later, Gitlab now has the capability to data as actual files which simplifies this so I am marking that as the correct answer.

Almund
  • 5,695
  • 3
  • 31
  • 35
  • You should look into App Signing inside the google play console. – Oliver Aug 07 '18 at 11:16
  • 1
    I've seen that one but I didn't catch you can build without key. I expected you'll need an upload key or so anyway. I'll reread it and see if I can get it. Thanks! – Almund Aug 07 '18 at 12:00
  • Yes, you will still need a Keystore. But you can invalidate this keystore in the play console, if it gets leaked, because this one isn't your "real" keystore. – Oliver Aug 07 '18 at 12:15

5 Answers5

51

Usually I store keystore file (as base64 string), alias and passwords to Gitlab's secrets variables.

In the .gitlab-ci.yml do something like:

create_property_files:
  stage: prepare
  only:
    - master
  script:
    - echo $KEYSTORE | base64 -d > my.keystore
    - echo "keystorePath=my.keystore" > signing.properties
    - echo "keystorePassword=$KEYSTORE_PASSWORD" >> signing.properties
    - echo "keyAlias=$ALIAS" >> signing.properties
    - echo "keyPassword=$KEY_PASSWORD" >> signing.properties
  artifacts:
    paths:
      - my.keystore
      - signing.properties
    expire_in: 10 mins

And, finally, in your build gradle:

signingConfigs {
    release {
        file("../signing.properties").with { propFile ->
            if (propFile.canRead()) {
                def properties = new Properties()
                properties.load(new FileInputStream(propFile))

                storeFile file(properties['keystorePath'])
                storePassword properties['keystorePassword']
                keyAlias properties['keyAlias']
                keyPassword properties['keyPassword']
            } else {
                println 'Unable to read signing.properties'
            }
        }
    }
}
IvanP
  • 1,064
  • 13
  • 16
9

From IvanP's answer:

Usually I store keystore file (as base64 string), alias and passwords to Gitlab's secrets variables.

That should be easier with GitLab 15.0 (May 2022)

Project-level Secure Files in open beta

Previously, it was difficult to use binary files in your CI/CD pipelines because CI/CD variables could only contain text values. This made it difficult to make use of profiles, certificates, keystores, and other secure information, which are important for teams building mobile apps.

Today we are releasing Project-level Secure Files in open beta. Now you can upload binary files to projects in GitLab, and include those files in CI/CD jobs to be used in the build and release processes as needed. Secure Files are stored outside of version control and are not part of the project repository.

Please leave us your feedback about your experience with this feature in the feedback issue.

See Documentation and Issue.

VonC
  • 1,262,500
  • 529
  • 4,410
  • 5,250
  • Thank you @VonC, you are right this is definitely easier now and I am using the new file capability. – Almund May 24 '22 at 00:22
  • Thanks, @VonC, this is easier for sure. However, once you download the files they will remain in the repository unless you explicitly delete them. This means that, on a local gitlab runner, they will be kept on the machine until a new job cleans the repository. So, one could easily `cat` the files if they had permission to create a new job, right? – TheFabbius Oct 07 '22 at 15:20
  • 1
    @TheFabbius I suppose so, yes (not tested). I suppose this is where a proper cleanup at the end of a job completion would be important. – VonC Oct 07 '22 at 15:47
5

Thanks to @IvanP's answer but I had to make some changes in the Gitlab pipeline to get it working correctly.

Export the Keystore as a base64 string:

openssl base64 -A -in my.keystore > base64

Create a new variable and copy-paste the value in the file base64. Be careful while doing this, I screwed up the formatting and the pipeline was not able to decode the Keystore. The variables had to be referred to with ${} rather than just $.

- echo -n ${ANDROID_KEYSTORE} | base64 -d > my.keystore
- echo "keystorePath=../my.keystore" > myapp.properties
- echo "keystorePassword=${KEYSTORE_PASSWORD}" >> myapp.properties
- echo "keyAlias=${ALIAS_NAME}" >> myapp.properties
- echo "keyPassword=${KEY_PASSWORD}" >> myapp.properties

Refer to the myapp.properties in your build.gradle as explained above in the @IvanP's answer.

Satheesh
  • 10,998
  • 6
  • 50
  • 93
2

I've used git-secret in the past to check-in password protected secret files. Then pass the password via a secret/protected environmental variable (as you already know) and modify the .gitlab-ci.yml to use the password to open the files and use them.

mmccabe
  • 2,259
  • 1
  • 24
  • 25
  • Sounds reasonable, I've read in other places about using different repos and such to store the secret files. I'll give it a try and come back once it's done. Thank you! – Almund Aug 08 '18 at 13:47
  • Sure.. there is also a fastlane plugin that encrypts keystores https://github.com/hjanuschka/fastlane-plugin-cryptex (haven't tried). Plus here is a (dated) article about how NOT to manage certificates in Google Play Store https://researchcenter.paloaltonetworks.com/2014/08/bad-certificate-management-google-play-store/ – mmccabe Aug 08 '18 at 14:45
0

Another solution is to use android.injected properties:

  1. First encode the certificate file:
openssl enc -base64 -in certFile -out certFileEncoded
  1. Get the content of certFileEncoded file and define in the Gitlab settings as a file type variable.

  2. Inject certificate, alias and password in the assemble task:

script:
    - openssl enc -d -base64 -in $CERT_FILE_BASE_64 -out certFile
    - ./gradlew :app:assembleRelease -Pandroid.injected.signing.store.file=$(pwd)/certFile -Pandroid.injected.signing.store.password=$CERT_PASS -Pandroid.injected.signing.key.alias=$CERT_ALIAS -Pandroid.injected.signing.key.password=$CERT_PASS

Here the CERT_ALIAS and CERT_PASS are masked variables.

Derek K
  • 2,756
  • 1
  • 21
  • 37