İlk bölümde tasarımını yaptığımız JWT Token otomasyonunun ikinci bölümünde çözüm sürecini hayata geçiriyoruz. İlk bölüme https://www.mshowto.org/jwt-token-otomasyonu-bolum-1.html Linkinden ulaşabilirsiniz.
- JWT Token yapısı; (*1)
Öncelikle arayacağımız JWT Token’ın yapısını incelememiz gerekiyor. JWT Token aaaaa.bbbb.ccccc şeklinde nokta ile ayrılmış üç bölümden oluşmaktadır. Bu üç bölümden her bir bölüm Base64 formatında Encode edilmiştir.
- İlk bölüm Header olarak konumlandırılmıştır. Header Decode edilirse içerisinde JSON tipinde formatında iki adet parametre barındırdığı anlaşılır. Bunlardan bir tanesi olan “alg:” Token’ın hangi güvenlik algoritması ile şifrelendiğinin standardının yazdığı parametredir. Diğer parametre olan “typ:” içerisinde de Token tipi yazmaktadır ki bu da sabit olarak “JWT” olarak belirlenmiştir.
{"typ":"JWT","alg":"HS256"}
- İkinci bölüm Payload olarak adlandırılmıştır. Payload Decode edildiğinde içerisinde JSON formatında; Token’ı üreten “iss:” (Issuer), Konusu “sub:” (Subject), Amaçlanan kullanıcı kitlesi “aud:” (Audience), Geçerlilik başlangıç tarihi “nbf: ” (Not Before), Son kullanma tarihi “exp:” (Expiration Time), Verildiği tarih “iat:” (Issued at), Serbest olarak tanımlanabilecek kimlik numarası “jti:”(JWT ID) ve isteğe bağlı tanımlanabilecek diğer parametreler bulunan bölümdür.
{ "iss": "https://auth.domain.com", "aud": "https://api.domain.com", "nbf": 1576680628, "exp": 1609372800, "sub": "8e01d6ae-5f59-4593-aaba-3cc75c6d00ce", "role": "backoffice:app" }
- Son bölüm Code olarak adlandırılmıştır. Bu bölümde Header “alg:” parametresindeki şifreleme formatı ile formatın tipine göre, Gizli Kelime (Secret Key) veya Sertifika (Certificate) ile şifrelenmiş içerik bulunmaktadır. Bu içerik ilgili şifre çözme algoritmasından geçirilerek Token’ın geçerli olup olmadığını anlamak için kullanılır.
- Powershell’de (*2) RegEx (*3) Kullanarak dosyaların içerisinde arama yapmak;
Genel olarak yapısını öğrendiğimiz JWT Token’ları dosyalar içinde bulmak için RegEx kullanacağız. RegEx’i verdiğimiz koşula ve kurala uygun olan desenleri metin içerisinde bulmamızı sağlayan hızlı ve kullanışlı algoritma türü şeklinde basitçe tanımlayabiliriz. Regex desenleri ilk bakışta çok karışık ve kafa karıştırıcı görünmekle beraber programlamada birçok yerde hayat kolaylaştırıcı ve olmazsa olmazlardandır.
Bizim aradığımız JWT Token’lar aşağıdakine benzer şekilde;
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJsb2dnZWRJbkFzIjoiYWRtaW4iLCJpYXQiOjE0MjI3Nzk2Mzh9.gzSraSYS8EXBxLN_oWnFSRgCzcmJmMjLiuyu5CSpyHI
- “ey” harfleri ile başlayan,
- Daha önce bahsettiğimiz gibi
a.b.c şeklinde nokta ile ayrılmış Base64 kodlanmış üç bölümden oluşan - Her bölümünde içeriğinde Büyük Harf, Küçük Harf, Rakam, _, = olmak üzere en az 15 karakter barındıran ve uzunluğu en az baştaki “ey” ve iki adet nokta ile birlikte 49 karakter olan, ayrıca başlangıç ve bitişinde tek tırnak veya çift tırnak arasındaki karakterlerden oluşmuş desendir. Anlatması bile karışık ve beni yoran bu deseni dosyaların içerisinde aramak için PowerShell kullanacağız. Bunun için kullanacağımız Script aşağıdadır.
########################################################################### ## Find-Token ## Author: Bora Tercan ## Date : 21.01.2021 ## Version : 1.0 ## Email: btercan@hotmail.com ########################################################################### # Main Arrays Settings $BasePath=$PSScriptRoot $TokenFilePaths=Join-Path -Path $BasePath -ChildPath "FindTokenConfig.txt" $TokenFiles=Join-Path -Path $BasePath -ChildPath "TokenConfig.txt" $TokenFilesExtra=Join-Path -Path $BasePath -ChildPath "FixTokenConfig.txt" $TokenConfigs = Import-Csv -Path $TokenFilePaths $NewTokenConfigs=@() # Done Import-module (Join-Path -Path $BasePath -ChildPath "IsJwtToken.ps1") -Force foreach($TokenConfig in $TokenConfigs){ switch ($TokenConfig.Type) { "findtoken" { if (Test-Path $TokenConfig.Path) { $ScriptPaths=Get-ChildItem -Path $TokenConfig.Path -Recurse -Include *.ps1, *.txt, *.config, *.json #, *.js $Node=$TokenConfig.Node foreach($ScriptPath in $ScriptPaths.FullName) { $TokenLine=Select-String -path $ScriptPath -Pattern '[ey]{2,2}[A-Za-z0-9-_=]{15,}[.][A-Za-z0-9-_=]{15,}[.][A-Za-z0-9-_.+/=]{15,}[''"]' if ($ScriptPath -like "*\tokenPolicies.config" ) {$TokenLine="Exeption [*\tokenPolicies.config]"} if ($ScriptPath -like "*\tokenPolicies.csv" ) {$TokenLine="Exeption [*\tokenPolicies.csv]"} foreach($ScriptContent in $TokenLine) { if ($ScriptContent -notmatch 'Exeption(.*?)') { $OldToken = $ScriptContent.Matches.Value.Replace('"','').Replace("'","") $ParsedToken=IsJwtToken -Token $OldToken -iss "api.paximum.com" -Verbose if ($ParsedToken) { Write-Host "Findtoken: $ScriptPath ->" $OldToken.Substring(0,4) "..." $OldToken.Substring(250) -ForegroundColor Magenta Write-Host "-End----------------------------------------------------------------------------" -ForegroundColor Green switch ((dir $ScriptPath).Extension) { ".config" { $Type = "XML" $ScriptContent | ForEach-Object { if ( ($_ -match 'key="(.*?)"') -or ($_ -match "key='(.*?)'") ) { $Node=$Matches[1] } } } ".json" { $Type = "json" $ScriptContent | ForEach-Object { if ( ($_ -match '"(.*?)"') -or ($_ -match "'(.*?)'") ) { $Node=$Matches[1] } } } ".txt" { $Type = "PowerShell"; $Node="" } ".ps1" { $Type = "PowerShell"; $Node="" } default { $Type = "Unknown"; $Node="Unknown"} } $AddTokenConfig = new-object psobject -property @{ Name ="Autogenerate" Path ="$ScriptPath" Type ="$Type" Node ="$Node" Environment="live" Role =$ParsedToken.role ApiID =$ParsedToken.sub ExpireDate =([datetime]([timezone]::CurrentTimeZone.ToLocalTime(([datetime]'1/1/1970').AddSeconds($ParsedToken.exp)))).ToString("yyyy-MM-dd") LastToken ="" } $NewTokenConfigs += $AddTokenConfig } else { Write-Host " #-> This is not JWT Token [$ScriptPath] [$OldToken] !!!" -ForegroundColor Red #pause } } else { Write-Host " #-> There is no token in $ScriptPath or Exeption. Nothing to do !!!" -ForegroundColor Red } } } } } } Write-Host " * -> Type:"$TokenConfig.Type" - Path:"$TokenConfig.Path" - Node:"$Node $TokenConfig.ApiID $TokenConfig.Environment $TokenConfig.Role $TokenConfig.ExpireDate -ForegroundColor Cyan } if (Test-Path ( $TokenFiles + "_old" ) ) {Remove-Item -LiteralPath ( $TokenFiles + "_old" ) } if (Test-Path $TokenFiles) {Rename-Item -LiteralPath $TokenFiles -NewName ((Split-Path $TokenFiles -leaf) + "_old") } $TokenConfigs = Import-Csv -Path $TokenFilesExtra foreach($TokenConfig in $TokenConfigs){ $NewTokenConfigs += $TokenConfig Write-Host " * -> [Add Extra Config] Type:"$TokenConfig.Type" - Path:"$TokenConfig.Path" - Node:"$Node $TokenConfig.ApiID $TokenConfig.Environment $TokenConfig.Role $TokenConfig.ExpireDate -ForegroundColor Cyan } $NewTokenConfigs | Select-Object Name,Path,Type,Node,Environment,Role,ApiID,ExpireDate,LastToken | Export-Csv -Path $TokenFiles -NoTypeInformation
- Powershell Script’in 27. Satırındaki Select-String fonksiyonunun –Patern parametresinde ‘[ey]{2,2}[A-Za-z0-9-_=]{15,}[.][A-Za-z0-9-_=]{15,}[.][A-Za-z0-9-_.+/=]{15,}[””]’
RegEx formülünü kullanarak dosyaların içerisinde Token’ları bulacağız. - Bulduğumuz Token adaylarının gerçekten JWT Token olup olmadığını da ayrı bir Powershell Function ile sınayacağız. İlgili Function Find-Token.ps1 isimli scriptin 18. Satırında import edilip 33. Satırda da kullanılmaktadır. IsJwtToken isimli Function da aşağıdadır.
########################################################################### ## IsJwtToken ## Author: Bora Tercan ## Date : 21.01.2021 ## Version : 1.0 ## Email: btercan@hotmail.com # https://www.michev.info/Blog/Post/2140/decode-jwt-access-and-id-tokens-via-powershell ########################################################################### function IsJwtToken { Param( [Parameter(Mandatory=$True, ParameterSetName="Token")] [string]$Token, [string]$iss="auth.domain.com" ) Process { $Istokenheader = $false $IstokenPayload = $false if ($token.Split(".").Count -ne 3) {return $false} if ($token.Length -lt 250) {return $false} #Header $tokenheader = $token.Split(".")[0].Replace('-', '+').Replace('_', '/') while ($tokenheader.Length % 4) { Write-Verbose "Invalid length for a Base-64 char array or string, adding ="; $tokenheader += "=" } $Istokenheader=$tokenheader -match '^([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}==)?$' Write-Verbose $Istokenheader if ($Istokenheader) { $tokenheaderArray = [System.Text.Encoding]::ASCII.GetString([system.convert]::FromBase64String($tokenheader)) Write-Verbose $tokenheaderArray if ($tokenheaderArray -like "*JWT*") { #Payload $tokenPayload = $token.Split(".")[1].Replace('-', '+').Replace('_', '/') while ($tokenPayload.Length % 4) { Write-Verbose "Invalid length for a Base-64 char array or string, adding ="; $tokenPayload += "=" } $IstokenPayload=$tokenPayload -match '^([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}==)?$' Write-Verbose $IstokenPayload $tokenByteArray = [System.Convert]::FromBase64String($tokenPayload) $tokenArray = [System.Text.Encoding]::ASCII.GetString($tokenByteArray) Write-Verbose $tokenArray if ($tokenArray -notlike "*$iss*") { $IstokenPayload = $false Write-Verbose "There is not $iss JWT Token !!!" } else { $tokobj = $tokenArray | ConvertFrom-Json Write-Verbose $tokobj } } } if ($Istokenheader -and $IstokenPayload) {return $tokobj} else {return $false} } }
- 1. maddede bahsettiğim JWT Token yapısına uygun olarak nokta olan yerlerden fonksiyonun 20. satırında parçalayıp, parçaların sayısının üç adet olduğunu doğrulayacağım.
- Sınamayı geçen ilk parça olan Header bölümünün Base64 (*4) deseninde olduğunu ‘^([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}==)?$’ 25. Satırda RegEx ile doğrulayacağım.
- Header bölümü kontrolü başarılı şekilde geçerse 28. satırda Base64 olarak Decode ettikten sonra 30. Satırda Header kısmında “JWT” olup olmadığına bakacağım.
- Başarılı olursa 34. Satırda ikinci parça olan Paylod bölümünün de Base64 deseninde olduğunu ‘^([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}==)?$’ RegEx ile doğrulayıp, başarılı ise 36. Satırda Base64 olarak Decode edeceğim.
- Decode ettiğim Payload kısmında “iss:” (Issuer) bizim aradığımız üretici (varsayılan olarak “auth.domain.com”) olup olmadığına bakacağım.
- Son olarak tüm kontrolleri geçen Token’ı Json’dan Powershell Object’e 43. Satırda çevirerek sonucu geri göndereceğim.
- Bu iki scripti kullanarak FindTokenConfig.txt dosyasında tanımlanış klasörlerin içindeki dosyalarda Token aradık ve bulduğumuz Tokenları TokenConfig.txt dosyasına yazdık ve FixTokenConfig.txt dosyasında olan ekstra Token bilgilerini de bulduğumuz dosyaya ekledik.
Bu bölümü de uzamaması için burada kesiyorum. Makalenin sonraki bölümünde de bulduğumuz ve TokenConfig.txt içerisine kaydettiğimiz Token’lardan değişmesi gerekenleri tespit ederek değiştirecek otomasyonu inceleyeceğiz. Ayrıca bir sonraki bölümde tüm dosyaları içeren Github Repository (*5) linkini de paylaşacağım. İlginizi çektiyse üçüncü bölümde görüşmek üzere.
Referanslar
- JWT (JSON Web Token): https://tr.wikipedia.org/wiki/JSON_Web_Token
- PowerShell: https://www.mshowto.org/powershell-nedir.html, https://www.mshowto.org/windows-powershell-nedir-ne-amacla-kullanilir-komutlar-nelerdir.html, https://docs.microsoft.com/tr-tr/powershell/
- RegEx (Regular expression): https://en.wikipedia.org/wiki/Regular_expression
- Base64: https://medium.com/@gokhansengun/base64-encoding-nedir-ve-nerelerde-kullan%C4%B1l%C4%B1r-d82f5307ea6d
- Github: https://www.mshowto.org/github-nedir-uyelik-nasil-olusturulur.html
- www.mshowto.org
TAGs: Otomasyon, JWT Token, DevOps, API, Powershell, AWS Powershell, XML, JSON, CSV, TEXT.