Ana içeriğe geç

Kimlik Doğrulama, Proxy ve TLS

1.1.0 ile geldi. Yük testi istekleri Basic, Bearer veya OAuth2 client credentials kimlik doğrulaması taşıyabilir; bu, kodda LoadAuth ile ayarlanır. Ağ ayarları, yani bir HTTP proxy ile TLS trust ve key store'ları, testfly.yml içinde loadtest.proxy ve loadtest.tls altında ayarlanır. Bu sayfadaki her şey iki motorda da çalışır; farklar sonda listelenmiştir. Tasarım, depodaki docs/adr/0008-load-test-auth-proxy-tls.md (ADR 0008) dosyasında kayıtlıdır.

Gizli bilgiyi asla koda veya YAML'a yazmayın

Parolaları, token'ları ve client secret'ları ortamdan okuyun: Java'da System.getenv("NAME"), testfly.yml içinde ${NAME} yer tutucuları. Bu sayfadaki örneklerin hepsi böyle yapar.


1. Kimlik doğrulama​

LoadAuth değiştirilemez (immutable) bir değerdir. Bir senaryoda auth(..) ile ayarlanınca her adımın varsayılanı olur; bir adımda ayarlanınca senaryonunkini ezer.

Fabrika metoduGönderilenNerede
LoadAuth.basic(user, password)Authorization: Basic base64(user:password)senaryo veya adım
LoadAuth.bearer(token)Authorization: Bearer <token>senaryo veya adım
LoadAuth.oauth2ClientCredentials(tokenUrl, clientId, clientSecret)koşu başına tek token ile Authorization: Bearer <token> (bkz. OAuth2)yalnızca senaryo
LoadAuth.fromConfig(name)testfly.yml içindeki api.auth.<name> girdisi (basic, bearer, oauth2 tipleri)senaryo veya adım (adımda oauth2 girdisi olmaz)
LoadAuth.none()kimlik doğrulama yokadım (noAuth())

Kısayollar: LoadScenario.basicAuth(user, password), LoadScenario.oauth2ClientCredentials(tokenUrl, clientId, clientSecret, scope) (null scope hiç scope göndermez), LoadStep.basicAuth(user, password) ve LoadStep.noAuth(). Argümansız auth() ayarlanmış değeri veya null döndürür.

Bir LoadAuth nesnesinin toString() çıktısı türü, kullanıcı adını veya client id'yi ve sorgusu çıkarılmış token URL'ini gösterir; her parola, token ve secret için *** yazar.

Basic ve Bearer​

TestFly, Basic başlığını iki motorda da kendisi, user:password metninin UTF-8 baytlarından oluşturur (RFC 7617); böylece iki motor da aynı baytları gönderir. Kullanıcı adı, parola veya token içindeki ${var} yer tutucuları her istekte feeder sütunlarından veya çıkarılan (extract) değişkenlerden çözümlenir; böylece her sanal kullanıcı kendi hesabıyla giriş yapabilir:

import io.testfly.loadtest.BaseLoadTest;
import io.testfly.loadtest.LoadAuth;
import io.testfly.loadtest.LoadTestFeeder;
import org.testng.annotations.Test;

public class OrdersAuthLoadTest extends BaseLoadTest {
@Test
public void ordersPerUser() {
loadScenario("Orders")
.feed(LoadTestFeeder.csv("testdata/users.csv")) // sütunlar: user,password
.basicAuth("${user}", "${password}") // her adımın varsayılanı
.step("List orders").get("/orders").and()
.step("Health").get("/health").noAuth().and() // kimlik bilgisi olmadan gönderilir
.step("Admin stats").get("/admin/stats")
.auth(LoadAuth.bearer(System.getenv("ADMIN_TOKEN"))).and()
.run().assertErrorRateBelow(0.01);
}
}

İstek oluşturulurken bir kimlik bilgisinde hâlâ çözümlenmemiş bir ${var} varsa (örneğin bir CSV satırında sütun eksikse) istek gönderilmez ve -1 durumuyla KO sayılır. Koşu devam eder. Çözümlendikten sonra : içeren bir kullanıcı adı veya kontrol karakteri (sekme dışında 0x20 altındaki herhangi bir karakter ya da DEL) içeren çözümlenmiş bir değer de aynı şekilde KO -1 olur. Bu, başlık enjeksiyonunu engeller ve değer hiçbir mesajda görünmez.

Öncelik​

En özel ayar kazanır:

Adımdaİstek şunu gönderir
auth(..) veya basicAuth(..)adımın kimlik doğrulaması
noAuth()kimlik doğrulama yok
header("Authorization", ..) var, adım auth'u yoko başlık; senaryonun auth'u eklenmez
auth(..) ve header("Authorization", ..)koşudan önce reddedilir (IllegalArgumentException)
hiçbirivarsa senaryonun auth(..) ayarı

OAuth2 client credentials​

import io.testfly.loadtest.BaseLoadTest;
import io.testfly.loadtest.LoadAuth;
import org.testng.annotations.Test;

public class CatalogOAuth2LoadTest extends BaseLoadTest {
@Test
public void searchWithServiceToken() {
loadScenario("Catalog search")
.auth(LoadAuth.oauth2ClientCredentials(
"https://auth.example.com/oauth2/token",
System.getenv("CATALOG_CLIENT_ID"),
System.getenv("CATALOG_CLIENT_SECRET"))
.scope("catalog.read")
.clientSecretBasic()) // isteğe bağlı; varsayılan client_secret_post
.step("Search").get("/catalog?q=shoes").and()
.run().assertErrorRateBelow(0.01);
}
}
  • Koşu başına tek token. Tüm sanal kullanıcılar onu paylaşır. Bir simülasyonda, token URL'i, client id'si, scope'u ve client kimlik doğrulaması aynı olan senaryolar tek token'ı paylaşır.
  • Yükten önce alınır. İlk token doğrulamadan sonra ve herhangi bir yük isteğinden önce istenir. Gatling'de bu, test JVM'inde, koşu slotundan, koşu dizininden ve alt JVM'den önce olur. Başarısız olursa run() exception fırlatır ve hiçbir yük isteği gönderilmez. Mesaj, sorgusu ve kullanıcı bilgisi çıkarılmış token URL'ini, HTTP durumunu ve OAuth2 error kodunu içerir; örneğin [LoadTest] OAuth2 token request to https://auth.example.com/oauth2/token failed: HTTP 401 (error=invalid_client). Yanıt gövdesini, error_description değerini, client id'yi, secret'ı veya bir token'ı asla içermez.
  • Arka planda yenilenir. Token, alındıktan expires_in − min(60 sn, expires_in / 5) sonra, en erken 1 sn sonra yenilenir. expires_in yoksa veya 0 ya da negatifse 3600 sn sayılır. İstekler yenilemeyi hiç beklemez. Başarısız bir yenileme 250 ms'den 2 sn'ye kadar artan aralıklarla yeniden denenir ve eski token gerçekten sona erene kadar kullanılmaya devam eder.
  • Yenileme başarısız olursa koşu iptal edilir. Gerçek sona erme anından sonra geçerli token kalmazsa koşu, iki motorda da IllegalStateException (… aborted: OAuth2 token refresh failed for <tokenUrl>: HTTP <code>) ile iptal edilir. Bu durum KO istekler olarak raporlanmaz.
  • İstek. Token URL'ine POST; form kodlu grant_type=client_credentials, ayarlandıysa scope. Client, client_id ve client_secret değerlerini formda gönderir (client_secret_post) veya .clientSecretBasic() sonrasında bir Authorization: Basic başlığıyla kimliğini doğrular. Yalnızca boş olmayan bir access_token içeren HTTP 200 kabul edilir; bearer dışında bir token_type (büyük/küçük harf fark etmez) reddedilir; refresh_token yok sayılır. İstek loadtest.proxy, loadtest.tls ve requestTimeoutSeconds ayarlarını kullanır.
  • Değişken değildir. Token ${access_token} veya başka bir değişken olarak sunulmaz. Alışılmadık bir yerleşim için (özel bir başlık, bir sorgu parametresi) token'ı bir adım ve extract(..) ile kendiniz isteyin; bkz. Fluent API sayfasındaki "Çok adımlı akış" örneği.
  • Metriklerde yer almaz. Token istekleri LoadTestMetrics içinde sayılmaz: toplamlarda, adımlarda, SLA'larda ve baseline'larda yoktur. Bunun yerine her koşu tek bir INFO satırı loglar: [LoadTest] '<scenario>': OAuth2 token requests=<n> refreshFailures=<m>.
  • Yalnızca senaryo düzeyi. OAuth2 client credentials ile LoadStep.auth(..) çağrısı IllegalArgumentException fırlatır.

fromConfig ile api.auth kullanımı​

LoadAuth.fromConfig(name), testfly.yml içindeki api.auth.<name> girdisini kullanır; API testleriyle aynı bloktur (bkz. Kimlik Doğrulama). Yalnızca basic, bearer ve oauth2 tipleri desteklenir; başka bir tip, koşudan önce bu üç tipi listeleyen bir mesajla başarısız olur. Girdi koşu doğrulanırken çözümlenir ve ${ENV} yer tutucuları her zamanki gibi çözümlenir. Hâlâ tam bir ${name} olan Basic veya Bearer değeri, her istekte bir yük testi değişkeni olarak çözümlenir; hâlâ yer tutucu olan bir oauth2 değeri reddedilir.

api:
auth:
catalogService:
type: oauth2
tokenUrl: https://auth.example.com/oauth2/token
clientId: ${CATALOG_CLIENT_ID}
clientSecret: ${CATALOG_CLIENT_SECRET}
loadScenario("Catalog search")
.auth(LoadAuth.fromConfig("catalogService").scope("catalog.read"))
.step("Search").get("/catalog?q=shoes").and()
.run();

api.auth girdisinde scope yoktur; scope'u .scope(..) ile ayarlayın. oauth2 olmayan bir girdide .scope(..) ve .clientSecretBasic() reddedilir. @UseAuth yük testlerine uygulanmaz: fromConfig kullanın.

Yönlendirmeler​

Bir adım yönlendirmeyi (redirect) izlediğinde kimlik bilgileri başka bir origin'e asla gönderilmez. Gatling'de aynı origin'e giden bir yönlendirme adımı da kimlik bilgisi olmadan gider; JDK motoru aynı origin'deki adımda onları korur.

Doğrulama​

Aşağıdakiler herhangi bir istekten önce IllegalArgumentException ile reddedilir. Mesajlar anahtar ve değişken adlarını verir, hiçbir değeri vermez.

  • Basic: null kullanıcı adı veya parola, boş kullanıcı adı, : içeren sabit (literal) bir kullanıcı adı ve iki değerden birinde #{ (yalnızca Gatling'e özgü sözdizimi; ${var} kullanın).
  • Bearer: boş bir token veya #{ içeren bir token.
  • OAuth2: mutlak bir http veya https URL'i olmayan ya da kullanıcı bilgisi veya fragment içeren bir token URL'i; boş client id veya secret; token URL'i, client id, client secret veya scope içinde ${ ya da #{ (token sanal kullanıcı başına değil, koşu başına bir kez istenir); tek boşlukla ayrılmış RFC 6749 scope token'ları olmayan bir scope; adımda OAuth2.
  • fromConfig: api.auth içinde olmayan bir ad veya desteklenmeyen bir tip.
  • Bir adımda ikisi birden: kendi auth'u ve bir Authorization başlığı (ad büyük/küçük harf duyarsız) olan bir adım.
  • URL'de kimlik bilgisi: herhangi bir auth ayarlıyken kullanıcı bilgisi içeren bir senaryo baseUrl değeri (https://user:pass@host). basicAuth(..) kullanın.

2. Proxy (loadtest.proxy)​

loadtest:
proxy:
host: proxy.corp.example
port: 3128
username: ${LOAD_PROXY_USER}
password: ${LOAD_PROXY_PASSWORD}
nonProxyHosts: [localhost, "*.internal.example"]
AnahtarTipVarsayılanAçıklama
proxy.hoststringboşŞema, yol veya @ içermeyen proxy host adı veya IP'si. Boş, yapılandırılmış proxy yok demektir.
proxy.portint01 ile 65535 arası; host ile birlikte zorunludur.
proxy.usernamestringboşProxy kullanıcısı. username ve password değerlerinin ikisini birden ayarlayın ya da hiçbirini ayarlamayın.
proxy.passwordstringboşProxy parolası. Bir ${ENV} yer tutucusu kullanın.
proxy.nonProxyHostslistboşDoğrudan erişilen host'lar. api.proxy içindeki gibi * joker karakterdir.
  • Açık proxy kazanır. Host ayarlıyken yük istekleri ve OAuth2 token istekleri iki motorda da bu proxy'den geçer ve JVM proxy özellikleri (http.proxyHost, https.proxyHost, http.nonProxyHosts) yok sayılır.
  • Proxy kimlik bilgileri yalnızca proxy'ye gider. Hedefe asla gönderilmez. İki motor da bunları önceden (preemptive) gönderir: düz HTTP isteklerinde ve HTTPS tünelini açan CONNECT isteğinde Proxy-Authorization olarak.
  • api.proxy değerine geri dönülmez. loadtest.proxy ile api.proxy ayrıdır; böylece yük, API testleri için yapılandırdığınız bir proxy'ye asla yönlendirilmez.
  • Ayarsız. loadtest.proxy.host yoksa önceki sürümlere göre hiçbir şey değişmez: JDK motoru JVM proxy özelliklerine uyar, TestFly Gatling'de proxy ayarlamaz.
  • Gatling'de joker karakterler. Gatling atlanacak host'ları birebir eşleştirir. TestFly, nonProxyHosts içindeki sabit girdileri ve bir joker karakterle eşleşen bilinen her host'u aktarır: senaryo baseUrl değeri, token URL'i ve sabit mutlak adım URL'leri. Yalnızca koşu sırasında bilinen bir host, örneğin https://${host}/x, bir joker karakterle yalnızca JDK motorunda eşleşir.
Proxy kimlik bilgileri ve HTTPS ile bilinen JDK sınırı
  • JDK, JVM genelindeki jdk.http.auth.tunneling.disabledSchemes özelliği izin vermedikçe bir CONNECT tüneli için Basic kimlik doğrulamasını reddeder (varsayılan olarak Basic kapalıdır ve JDK bu durumda Proxy-Authorization başlığını CONNECT isteğinden çıkarır). TestFly bu özelliği değiştirmez. Proxy kimlik bilgileri ile bir HTTPS hedefi bir aradaysa TestFly koşu başına bir WARN loglar. Bu, JDK motorunu ve iki motordaki OAuth2 token isteklerini etkiler.

Aşağıdakiler herhangi bir istekten önce IllegalArgumentException ile reddedilir: şema, /, @ veya boşluk içeren bir host; 1..65535 dışında bir port; username ve password değerlerinden yalnızca biri; host olmadan port, username veya password; boş bir nonProxyHosts girdisi; hâlâ tam bir ${NAME} yer tutucusu olan bir parola (loadtest.proxy.password references unset variable NAME).


3. TLS (loadtest.tls)​

loadtest:
tls:
trustStore:
path: certs/load-trust.p12
password: ${LOAD_TRUSTSTORE_PASSWORD} # type varsayılanı PKCS12
keyStore: # mTLS istemci sertifikası
path: certs/load-client.p12
type: PKCS12
password: ${LOAD_KEYSTORE_PASSWORD}
AnahtarTipVarsayılanAçıklama
tls.trustStore.pathstringboşTrust store dosyası. Varsayılan güveni değiştirir.
tls.trustStore.typestringPKCS12PKCS12 veya JKS.
tls.trustStore.passwordstringboşStore parolası. Bir ${ENV} yer tutucusu kullanın.
tls.keyStore.pathstringboşmTLS için istemci key store'u.
tls.keyStore.typestringPKCS12PKCS12 veya JKS.
tls.keyStore.passwordstringboşStore parolası; özel anahtar aynı parolayla açılmalıdır.
tls.insecureTrustAllbooleanfalseHer sunucu sertifikasına güvenir. Host adı yine doğrulanır.
  • Doğrulama. Bir trustStore veya keyStore ayarlıyken iki motor da sertifika zincirini ve host adını doğrular.
  • Trust store varsayılan güveni değiştirir. Token istekleri aynı ayarları kullandığı için OAuth2 token URL'inin CA'sı da içinde olmalıdır.
  • trustStore olmadan bir keyStore JVM'in varsayılan güvenini kullanır.
  • Anahtar parolası. Gatling tek bir parola aldığı için özel anahtar store parolasıyla açılmalıdır. TestFly bunu iki motorda da koşudan önce denetler. Anahtarı alias ile seçmek desteklenmez.
  • Koşudan önce denetlenir. Store'lar herhangi bir istekten önce yüklenir. Eksik dosya, yanlış parola, bilinmeyen tip, özel anahtarı olmayan bir key store ve sertifikası olmayan bir trust store, anahtarı ve yolu adlandıran ama parolayı asla vermeyen bir IllegalArgumentException ile başarısız olur. Örnek: [LoadTest] loadtest.tls.keyStore: cannot load 'certs/load-client.p12' as PKCS12 (wrong password or corrupt file).
  • insecureTrustAll: true her sunucu sertifikasına güvenir ama iki motorda da host adını denetlemeye devam eder. Koşu başına bir kez WARN loglar ([LoadTest] loadtest.tls.insecureTrustAll: server certificates are NOT verified (hostname verification stays on)). trustStore ile birlikte kullanılamaz. Yalnızca test ortamlarına karşı kullanın.
  • api.ssl değerine geri dönülmez. Proxy'de olduğu gibi loadtest.tls ayrıdır.
loadtest.tls olmadan Gatling sertifikaları doğrulamaz

loadtest.tls ayarlı değilse her motor kendi varsayılanını korur. JDK motoru sertifika zincirini ve host adını doğrular. Gatling 3.16'nın kendi varsayılanı her sertifikaya host adı denetimi olmadan güvenmektir. loadtest.tls olmadan Gatling'de https bir baseUrl için TestFly koşu başına bir INFO loglar: Gatling does not verify server certificates by default; set loadtest.tls.trustStore. Gatling'de doğrulama için loadtest.tls.trustStore ayarlayın. Bu varsayılanı değiştirmek, mevcut HTTPS Gatling koşularını bozacağı için 2.0'a ertelendi.

-Dgatling.ssl.* ve -Dgatling.http.enableHostnameVerification seçenekleri loadtest.gatling.jvmArgs içinde ayrılmıştır: TestFly bunları loadtest.tls üzerinden ayarlar.

Proxy ve TLS ayarları yalnızca testfly.yml dosyasından okunur; LoadScenario veya @LoadTest üzerinde karşılıkları yoktur. Bir simülasyon, resultsDir için olduğu gibi ilk senaryonun konfigürasyonunu kullanır. Kodda LoadTestConfig.getProxy() ve getTls() çözümlenen değerlerin kopyalarını döndürür.


4. Gizli bilgiler nasıl korunur​

  • Yer tutucular. testfly.yml içinde ${ENV} yer tutucuları yazın, kodda gizli bilgileri System.getenv(..) ile okuyun. Asla sabit bir gizli bilgi yazmayın.
  • Gatling alt JVM'i. Gizli bilgiler Gatling alt JVM'ine yalnızca koşunun geçici konfigürasyon dosyası üzerinden ulaşır; bu dosyayı yalnızca sahibi okuyabilir (POSIX'te 0600 izni; Windows'ta ek ACL yoktur). Alt JVM dosyayı başlangıçta okur ve siler; test JVM'i de koşu bitince yeniden siler. Gizli bilgiler asla komut satırına veya ortam değişkenlerine konmaz. Alt JVM'in ana sınıfı artık io.testfly.loadtest.internal.GatlingChildMain olur; bu sınıf TLS sistem özelliklerini süreç içinde ayarlar ve ardından Gatling'i başlatır.
  • Maskelenmiş alt süreç logu. gatling-subprocess.log maskeleyen bir filtreden geçirilerek yazılır: TestFly'ın bildiği her parola, client secret, token ve Basic kimlik bilgisi, Authorization ve Proxy-Authorization satırları ve tek başına Basic <base64> değerleri *** ile değiştirilir. Bu, Gatling'in HTTP trafiğini DEBUG düzeyinde loglayan bir logback-test.xml durumunu da kapsar.
  • Mesajlar ve toString(). Doğrulama ve çalışma zamanı mesajları anahtarları, değişkenleri, yolları ve kullanıcı bilgisi ile sorgusu çıkarılmış URL'leri verir, hiçbir değeri vermez. LoadAuth, loadtest.proxy ve store'lar toString() içinde parolalarını maskeler; LoadTestConfig.toString() proxy ve TLS ayarlarını göstermez.
  • Okunabilir kalanlar. Alt JVM parolaları koşu boyunca bellekte ve sistem özelliklerinde tutar. Aynı işletim sistemi kullanıcısına ait bir süreç bunları, örneğin jcmd ile okuyabilir. Bu, geçici dosyayla aynı güven sınırıdır. İki JVM de kill -9 ile öldürülürse geçici dosya geçici dizinde kalabilir.

5. Motor farkları​

ÖzellikJDK motoruGatling motoru
Basic ve BearerBaşlık her istekte oluşturulurAynı baytlar, her istekte TestFly tarafından oluşturulur
Çözümlenmemiş kimlik bilgisi ${var}KO, durum -1, hiçbir şey gönderilmezAynı
OAuth2 client credentialsToken test JVM'inde alınır ve yenilenirToken test JVM'inde alınır, alt JVM'de yenilenir
Aynı origin'e yönlendirme adımıKimlik bilgilerini korurKimlik bilgisi olmadan gönderilir
Proxy kimlik bilgileriÖnceden gönderilir; HTTPS için CONNECT isteğinde de (Basic'e izin vermek için jdk.http.auth.tunneling.disabledSchemes gerekir)Önceden gönderilir
nonProxyHosts joker karakterleriHer host için eşleşirKoşudan önce bilinen host'lar için eşleşir
loadtest.proxy ayarsızJVM proxy özellikleriTestFly proxy ayarlamaz
loadtest.tls ayarsızZinciri ve host adını doğrularHer sertifikaya güvenir, host adı denetimi yok (Gatling varsayılanı)
loadtest.tls ayarlıZinciri ve host adını doğrularAynı

1.1.0'da desteklenmeyenler​

Sonraki bir sürüme ertelendi: api.auth içindeki digest, HMAC ve apiKey girdileri (API anahtarı için header(..) kullanın); OAuth2 password ve refresh-token grant'ları; sanal kullanıcı başına token; SOCKS proxy'ler; senaryo başına proxy veya TLS ayarları; sanal kullanıcı başına istemci sertifikası; Gatling'in varsayılan olarak sertifika doğrulaması; TLS protokol ve şifre takımı (cipher) ayarları; geçici konfigürasyon dosyası için Windows ACL'i; metriklerde token istekleri.