Servis Hesapları ile OAuth2 Token Oluşturmak

15 Mart 2018

GCP servis hesapları son kullanıcıya ihtiyaç duymadan Google API'leri ile iletişim kurmanızı sağlayan kimlik doğrulama ve yetkilendirme birimleridir. Kimlik doğrulama bir istemcinin kim olduğu bilgisini, yetkilendirme ise neler yapabileceği bilgisini içerir.

Bir uygulamada servis hesapları genellikle, kullanıcı özel verisinden ziyade uygulamaya özgü veriye erişmek istendiğinde kullanıcıyı aradan çıkararak sunucudan sunucuya iletişim için kullanılır.

Örneğin, Google Cloud Storage kullanarak dosya yazıp okuma işlemleri yapan bir uygulama yazmak istiyorsunuz. Ek olarak, Gcs'deki dosyalarınızın herkese açık olması yerine, sadece uygulamanız tarafından erişilebilir olmasını istiyorsunuz. Rollerinde Gcs üzerinde gerekli yetkileri olan bir servis hesabı oluşturup, bu servis hesabının anahtarını sunucunuza ekleyerek okuma/yazma işlemlerini sunucular üzerinden (kullanıcıya ihtiyaç duymadan) halledebilirsiniz. Bu sayede kullanıcı tarafında herhangi bir hassas veri kaydetmenize de gerek kalmaz.

{
  "headers": {
    "normalizedNames": {},
    "lazyUpdate": null
  },
  "status": 401,
  "statusText": "Unauthorized",
  "url": "urlToService",
  "ok": false,
  "name": "HttpErrorResponse",
  "message": "Http failure response for urlToService: 401 Unauthorized",
  "error": {
    "error": {
      "errors": [
        {
          "domain": "global",
          "reason": "required",
          "message": "Anonymous caller does not have storage.objects.create access to bucket bucketName.",
          "locationType": "header",
          "location": "Authorization"
        }
      ],
      "code": 401,
      "message": "Anonymous caller does not have storage.objects.create access to bucket bucketName."
    }
  }
}
Gcs'de tüm kullanıcılara açık olmayan bir klasöre dosya yazımı sırasında Authorization başlığı kullanılmadığında alınan hata

Kullanıcı Tarafından Yönetilen Servis Hesapları

Google'ın otomatik olarak tanımladığı servis hesaplarına ek olarak kullanıcı tarafından tanımlanan servis hesaplarıdır. Genellikle, güvenliğin ikinci planda kaldığı (örneğin yerel ve şirket içi ortamlarda) veya Google harici başka bulut sunucularınında çalışılıyorsa bu tip servis hesaplarına başvurulur.

[SERVİS_HESAP_ADI]@[PROJE_ID].iam.gserviceaccount.com
Kullanıcı Tarafından Yönetilen Servis Hesabı Id Formatı

Google Bulut Platformu konsolunu kullanarak sadece birkaç adımda servis hesabı oluşturmak mümkün.

  • Menüyü kullanarak Servis Hesapları sayfasına girin.
  • Servis Hesabı Oluştur butonuna tıklayın.
  • Açılan pencerede
    • Servis hesabının yetkilerini tanımlayan bir isim girin.
    • Servis hesabının yerine getirmesini istediğiniz rolleri girin.
    • Anahtar dosyası indirmek için Yeni Bir Özel Anahtar Ver onay kutusunu işaretleyin.
      • Özel anahtar için bir dosya formatı (p12 veya json) seçin.
    • Oluştur butonuna tıklayın.
  • Anahtar dosyasını kaydedin, daha sonra Google API çağrılarını yetkilendirmede kullanacaksınız.
Servis hesabı oluşturma penceresiServis hesabı oluşturma penceresi

Google Api Client kütüphanelerini kullanarak anahtar dosyayı işlemek mümkün. Aşağıdaki örnekler java dili ile Google Api Java Client kütüphanesi kullanılarak oluşturulmuştur.

Credential credential = GoogleCredential.fromStream(keyFileInputStream).createScoped(Collections.singleton("https://www.googleapis.com/auth/devstorage.read_write"), httpTransport, jsonFactory);
storage = new Storage.Builder(httpTransport, jsonFactory, credential).build();
Json anahtar dosyası kullanılarak storage.dev.read_write kapsamında oluşturulan kimlik objesi.
Servis hesabının rollerinde Storage Object Admin rolü tanımlı olmalı.

Aynı işlem p12 formatında bir anahtar dosyası ve GoogleCredential.Builder metodunu kullanarak da gerçekleştirilebilir.

credential = new GoogleCredential.Builder()
  .setTransport(httpTransport)
  .setJsonFactory(jsonFactory)
  .setServiceAccountId(serviceAccountId)
  .setServiceAccountPrivateKeyFromP12File(p12File)
  .setServiceAccountScopes(scopes)
  .addRefreshListener(refreshListener)		
  .build();
storage = new Storage.Builder(httpTransport, jsonFactory, credential).build();
p12 anahtar dosyası ile oluşturulan kimlik objesi.

Bir kullanıcının kimliğine bürünmek istenildiğinde setServiceAccountUser metodu kullanılabilir. Ortak bir data kümesinin tutulduğu, bir google hesabına gerek duyulan bir sisteme erişim için bu metoddan faydalanılabilir.

credential = new GoogleCredential.Builder()
  .setTransport(httpTransport)
  .setJsonFactory(jsonFactory)
  .setServiceAccountId(serviceAccountId)
  .setServiceAccountPrivateKeyFromP12File(p12File)
  .setServiceAccountScopes(scopes)
  .setServiceAccountUser("[email protected]")
  .build();
p12 anahtar dosyası ile kullanıcının kimliğine bürünülerek oluşturulan kimlik objesi.

Google Tarafından Yönetilen Servis Hesapları

Google tarafından otomatik olarak oluşturulup projeye atanan servis hesaplarıdır. Bu hesapların her biri farklı Google hizmetlerini temsil eder ve her birinin Google Cloud Platform projenize belirli bir düzeyde erişimi vardır.

Eğer uygulamanız Compute Engine, Kubernetes Engine, App Engine flexible environment, veya Cloud Functions üzerinde çalışıyorsa ayrıca bir servis hesabı oluşturmanıza gerek kalmadan Google tarafından otomatik olarak oluşturulmuş Compute Engine varsayılan servis hesabını kullanabilirsiniz.

[PROJE_NUMARASI][email protected]
Compute Engine Servis Hesabı Id Formatı

GoogleCredential credential = GoogleCredential.getApplicationDefault().createScoped(gcsScopes);
storage = new Storage.Builder(httpTransport, jsonFactory, credential).build();
Varsayılan compute engine servis hesabı kullanılarak oluşturulan kimlik objesi

Eğer uygulamanız Google App Engine standard environment üzerinde çalışıyorsa App Engine App Identity API kullanarak kimlik bilgilerine erişmek mümkün.

AppIdentityService appIdentity = AppIdentityServiceFactory.getAppIdentityService();
AppIdentityService.GetAccessTokenResult accessToken = appIdentity.getAccessToken(scopes);
String accessToken = accessToken.getAccessToken();
Varsayılan app engine servis hesabı kullanılarak oluşturulan kimlik objesi

Application Default Credentials

GoogleCredential.GetApplicationDefault() ile getirilen varsayılan kimlik bilgisi, ilk olarak GOOGLE_APPLICATION_CREDENTIALS çevre değişkeninin belirlenip belirlenmediğine bakar. Eğer belirlenmiş ise, karşılığındaki değerin gösterdiği dosyayı kullanır. Eğer belirlenmemiş ise Compute Engine, Kubernetes Engine, App Engine flexible environment, veya Cloud Functions sistemlerinin sağladığı kimlik bilgisini kullanır. Eğer iki durumda da kimlik bilgisine ulaşılamazsa, hata oluşur.

Özetlemek gerekirse,

  • 1. GOOGLE_APPLICATION_CREDENTIALS çevre değişkeninin değeri mevcut ise, işaret ettiği değer anahtar dosya olarak kullanılır.
  • 2. Projenin içinde bulunduğu sistemin sağladığı kimlik bilgisi kullanılır.
  • 3. İki durumda da kimlik bilgisi bulunamazsa hata oluşur.

Eğer Google App Engine içerisinde çalışılıyorsa ve varsayılan App Engine servis hesabı yerine elle oluşturulmuş kullanıcı tarafından yönetilen bir servis hesabı kullanmak isteniyorsa, kod içerisinden erişilmek istenen her kaynakta yapılması gerektiği gibi, anahtar dosyasının yolu appengine-web.xml dosyasında kaynak olarak belirtilmelidir. Aksi takdirde App Engine dosyayı bulamayacaktır.

java.io.IOException: Error reading credential file from environment variable GOOGLE_APPLICATION_CREDENTIALS,
value 'private_key.json': File does not exist. at
com.google.api.client.googleapis.auth.oauth2.DefaultCredentialProvider.runningUsingEnvironmentVariable(DefaultCredentialProvider.java:199) at
com.google.api.client.googleapis.auth.oauth2.DefaultCredentialProvider.detectEnvironment(DefaultCredentialProvider.java:166) at
com.google.api.client.googleapis.auth.oauth2.DefaultCredentialProvider.getDefaultCredentialUnsynchronized(DefaultCredentialProvider.java:110) at
com.google.api.client.googleapis.auth.oauth2.DefaultCredentialProvider.getDefaultCredential(DefaultCredentialProvider.java:91) at
com.google.api.client.googleapis.auth.oauth2.GoogleCredential.getApplicationDefault(GoogleCredential.java:213)
App Engine içerisinde alınan anahtar dosya bulunamadı hatası

<env-var name="GOOGLE_APPLICATION_CREDENTIALS" value="private_key.json" />

<resource-files>
<include path="/**.json" />
</resource-files>
appengine-web.xml içerisinde anahtar dosyayı kaynak dosya olarak göstererek kod içerisinden erişilebilir yapmak
Kök dosya içerisindeki tüm json uzantılı dosyaları ekler.
Kaynak dosyalar dışarıya kapalıdır.