Các thủ thuật để giấu secret key/password khỏi source control và ứng dụng với CI trong lập trình Android
Bài đăng này đã không được cập nhật trong 3 năm
Xin chào các bạn. Chắc hẳn mỗi chúng ta đều đã từng phát triển app sử dụng API của bên thứ 3, và chắc mọi người đều biết là hầu hết các API service đều yêu cầu chúng ta phải có 1 API key (secret token) để verify nguồn gốc của các request. Vậy thì tại sao chúng ta lại phải "giấu" API key này khỏi các VCS như git? Một trong những lý do quan trọng nhất chính là việc những người khác sẽ có toàn quyền sử dụng API dưới danh nghĩa của bạn, và thậm chí là họ có thể chỉnh sửa các dữ liệu bí mật hay violate các rule của service. Lí do thứ 2 là hầu hết các free service đều áp đặt 1 rate limit lên request; ví dụ 1 API key chỉ được phép thực hiện 60 request trong 1 phút, nên việc những người phát triển app của bạn cùng sử dụng 1 API key sẽ làm cho số request này hết rất nhanh và hiệu suất làm việc sẽ bị giảm đi đáng kể. Ở bài này chúng ta sẽ thảo luận về các cách để "giấu" API key mà không làm hỏng cấu trúc của ứng dụng khi làm việc theo team trên git, cũng như cách để áp dụng các thủ thuật này với các CI như Travis hay Circle.
Cách thường dùng
Cách phổ biến nhất để giấu API key chính là cho nó làm XML resource và đưa vào .gitignore
:
1. Đầu tiên chúng ta tạo 1 file XML mới, tạm đặt tên là keys.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="api_key">5093450734057234872347234</string>
</resources>
2. Đọc giá trị của key khi app chạy:
String apiKey = getString(R.string.api_key);
3. Cho path của file này vào .gitignore
để git không track nó.
4. Chỉnh sửa file Readme để hướng dẫn mọi người làm theo các bước trên.
Nhìn thì khá là đơn giản, nhưng cách này có rất nhiều nhược điểm:
- Rắc rối để chỉnh sửa key.
- Cần cho ra file riêng với tên chính xác là
keys.xml
và path chính xác để.gitignore
hoạt động đúng. Ngoài ra còn cần thêm thuộc tínhtranslatable="false"
khi sử dụng nhiều ngôn ngữ. - Sẽ không thể build pass CI vì lỗi thiếu resource.
Cách tốt hơn: Sử dụng gradle.properties
gradle.properties
là file chứa các setting để configure build environment của Gradle. File này sẽ được đọc ra khi gradle build project của bạn. Chúng ta sẽ lợi dụng điều này để định nghĩa các API key:
1. Thêm API key vào gradle.properties
:
API_KEY=5093450734057234872347234
2. Gán giá trị vào biến trong file build.gradle
của module (app/build.gradle
)
android {
defaultConfig {
buildConfigField "String", "API_KEY", "\"${API_KEY}\""
//Lí do chúng ta phải sử dụng \ là để kèm escape character " " vào chuỗi String
}
}
Khi build project, gradle sẽ tự generate ra 1 file tên BuildConfig.java
với các thông tin của build.gradle
:
public final class BuildConfig {
public static final String API_KEY = "5093450734057234872347234";
}
3. Sử dụng trong Java:
String apiKey = BuildConfig.API_KEY;
4. Chúng ta cần commit file này lên git, vì thế đầu tiên chúng ta phải "giấu" key thật đi như sau:
gradle.properties
API_KEY=INSERT_YOUR_API_KEY_HERE
5. git add
, git commit
và push
lên upstream như bình thường.
6. Sử dụng lệnh git update-index --skip-worktree gradle.properties
. Về cơ bản thì lệnh này sẽ nói với git là "Đừng quan tâm đến bất kì thay đổi nào trong file gradle.properties
, luôn luôn sử dụng file ở local và không pull những thay đổi từ upstream về đối với file này".
7. Sửa lại gradle.properties
để dùng lại API thực của chúng ta:
API_KEY=5093450734057234872347234
Sau đó bạn thử dùng git status
xem, git sẽ nói là không có bất kì thay đổi nào ở local đó là lí do chúng ta dùng lệnh skip-worktree
ở bước 6, từ giờ git sẽ coi như file gradle.properties
không tồn tại.
8. Edit README và hướng dẫn teammate thay API key của họ vào gradle.properties
.
Cách này tốt hơn cách trên bởi vì chúng ta có 1 nơi quy định để thay đổi API key. Hơn thế nữa là chúng ta có thể sử dụng key này ở bất cứ đâu trong app mà không cần context để getString()
, bởi vì key này sẽ được generate ra như 1 constant. CI cũng sẽ build thành công bởi vì lí do như trên.
A more interesting situation
Tôi muốn chia sẻ cho các bạn 1 vấn đề mà tôi mới gặp gần đây. Tôi muốn tích hợp Fabric vào ứng dụng của mình để sử dụng Crashlytics. Như thường lệ thì Fabric yêu cầu chúng ta phải có 1 API key và 1 API secret để nó có thể verify account. Ban đầu tôi cũng nghĩ chúng ta có thể sử dụng cách trên để đưa những key này vào gradle.properties
, nhưng không, Fabric yêu cầu chúng ta cung cấp key bằng 1 trong 2 kiểu sau:
1. Đặt key vào manifest
<meta-data
android:name="io.fabric.ApiKey"
android:value="io34jnto3i4nt34it3049g394g"
/>
2. Tạo file fabric.properties
trong module app
(app/fabric.properties
) với nội dung sau:
apiSecret=09df09bdfvskdvmp3olmr32pmưlkmvv
apiKey=io34jnto3i4nt34it3049g394g
Với cách #1 thì về cơ bản chúng ta vẫn làm theo các bước để thêm API vào gradle.properties
, nhưng bây giờ chúng ta sẽ phải sử dụng placeholder để file manifest có thể đọc ra giá trị từ build.gradle
như sau:
app/build.gradle
android {
defaultConfig {
manifestPlaceholders = [ apiKey:API_KEY ]
}
}
AndroidManifest.xml
<meta-data
android:name="io.fabric.ApiKey"
android:value="${apiKey}"
/>
Nếu không muốn sử dụng cách này thì chúng ta có thể chuyển sang cách 2. Với cách #2 thì chúng ta sẽ sử dụng gradle task để generate ra file fabric.properties
khi compile project:
1. Thực hiện các bước để thêm API key và API secret vào gradle.properties
như hướng dẫn ở trên.
2. Gradle lifecycle bao gồm 2 phase chính là configuration phase và execution phase. Trong configuration phase, các setting và task sẽ được thực thi, và build sẽ được chạy trong execution phase. Lợi dụng điểm này, chúng ta sẽ sử dụng hàm afterEvaluate
vốn sẽ được gọi khi configuration phase kết thúc để tạo ra file fabric.properties
với những thông tin cần thiết.
app/build.gradle
afterEvaluate {
//generate a file named fabric.properties with the api key and api secret when the project is being compiled
//edit gradle.properties to include your api key and secret under the name API_KEY and API_SECRET, respectively
initFabricProperties()
}
def initFabricProperties() {
def propertiesFile = file('fabric.properties')
if (!propertiesFile.exists()) {
def commentMessage = "This is autogenerated fabric property from system environment to prevent key from being committed to source control."
ant.propertyfile(file: "fabric.properties", comment: commentMessage) {
entry(key: "apiSecret", value: API_SECRET)
entry(key: "apiKey", value: API_KEY)
}
}
}
3. Chạy app. Bạn sẽ thấy là mặc dù chúng ta không có key trong manifest nhưng project sẽ vẫn compile và file fabric.properties
sẽ được tạo ra dưới đường dẫn app/fabric.properties
. Trong file này bạn sẽ thấy apiKey và apiSecret được định nghĩa.
4. Cho app/fabric.properties
vào .gitignore
để git không track file này.
Ứng dụng với CI
Trong bài viết này tôi sẽ nói về cách để chúng ta có thể build thành công project trên Travis CI mà không cung cấp API key lên git. Travis CI là 1 trong những CI service nổi tiếng và được sử dụng nhiều nhất vì nó rất dễ để cài đặt. Nếu bạn chưa biết Travis là gì thì hãy search Google nhé.
Tiếp tục vấn đề ở trên. Sau khi tôi đã tích hợp Fabric vào app, Travis sẽ không thể build thành công project. Tại sao lại như vậy, đó là do Fabric sẽ check key valid trong khi compile project, nếu key không đúng thì project sẽ không thể build được. Travis không hề biết về thông tin API key hay API secret vì tôi đã giấu nó khỏi Github.
Cách giải quyết vấn đề này thật ra khá đơn giản. Travis hỗ trợ chúng ta trong việc định nghĩa các environment variable để sử dụng khi build:
Ở đây chúng ta sẽ định nghĩa API key và API secret với prefix là ORG_GRADLE_PROJECT_ + tên của biến
. Sở dĩ chúng ta cần prefix này là bởi vì Gradle sẽ đọc ra từ environment variable những biến bắt đầu bằng prefix đó và sẽ add những biến đó vào properties của project của bạn (cũng giống như việc chúng ta thêm vào file gradle.properties
vậy).
Ở cột Name, thêm ORG_GRADLE_PROJECT_API_SECRET
và thêm key vào cột Value, sau đó ấn add. Làm tương tự với API_SECRET. Sau khi xong thì hãy thử restart build, Travis chắc chắn sẽ build thành công
Đây là 1 project tôi đã thử và thành công, các bạn có thể tham khảo tại https://github.com/b1acKr0se/bridddle-for-dribbble
Hi vọng các thủ thuật này sẽ giúp team của bạn cooperate tốt hơn trong việc phát triển ứng dụng cũng như đóng góp cho các open source project.
All rights reserved