마짱짱의 지식창고
[GCP] Binary Authorization을 이용하여 신뢰한 이미지만 GKE 배포 본문
Kubernetes Cluster 실행에 대한 주요 보안 문제 중 하나는 각 Pod 내에서 어떤 컨테이너 이미지가 실행되고 있는지 파악하고 해당 이미지의 출처를 설명할 수 있어야 합니다.
컨테이너의 출처를 설정 한다는 것은 컨테이너의 소스를 신뢰할 수 있는 원본 지점으로 추척할 수 있는 기능을 보유하고 조직이 아티팩트(컨테이너) 생성 중에 원하는 프로세스를 따르도록 보장하는 것을 의미합니다.
주요 관심사로는 다음과 같다.
1. Cluster에서 실행 중인 모든 컨테이너가 승인된 소스에서 왔는지
2. 모든 컨테이너 빌드 및 모든 배포에 대해 유효성 검사 단계가 성공적으로 완료 되었는지
3. 출처가 확인되고 컨테이너가 동작시 기존 빌드된 소스에서 수정되지 않았는지
이미지의 출처를 강제하지 않으면 다음과 같은 위험에 처할 수 있습니다.
1. 알수 없는 소스에서 다른 컨테이너를 시작할 수 있는 충분한 클러스터 권한을 얻을 수 있습니다.
2. Pod를 생성가능한 권한이 있는 사용자는 클러스터 내에서 컨테이너를 실수로 또는 악의적으로 실행할 수 있습니다.
3. 원하지 않는 코드가 자동으로 추가된 컨테이너로 도커 이미지 태그를 실수로 또는 악의적으로 덮어쓸 수 있으며 Kubernetes는 해당 컨테이너를 자동으로 배포 될 수 있습니다.
등 다양한 문제가 발생할 수 있는데 GCP에선 이러한 문제를 해결할 수 있도록 Binary Authorization이라는 기능을 제공합니다.
Binary Authorization은 배포 시점의 보안 제어를 제공하여 Google Kubernetes Engine(GKE) 또는 Cloud Run에 신뢰할 수 있는 컨테이너 이미지만 배포되도록 합니다. Binary Authorization을 사용하면 개발 프로세스 중에 신뢰할 수 있는 기관으로부터 이미지에 서명을 받도록 요구하고 이후 배포 시 서명 검증을 실시할 수 있습니다. 검증을 시행하면 확인된 이미지만 빌드 및 출시 프로세스에 통합되어 컨테이너 환경을 보다 효과적으로 관리할 수 있습니다.
해당 포스팅에서는 Binary Authorization 기능이 사용 설정된 GKE(Google Kubernetes Engine)에 배포하여 승인된 Container Registry를 허용 목록에 추가하는 방법을 보여주고 컨테이너를 만들고 실행하는 프로세스를 안내합니다.
GCP의 Binary Authorization은 OSS인 grafeas 및 Kritis를 기반으로 동작합니다.
- Grafeas는 컨테이너 이미지, 가상 머신(VM) 이미지, JAR 파일 및 스크립트와 같은 소프트웨어 리소스에 대한 메타데이터를 관리하기 위한 API 사양을 정의합니다. Grafeas를 사용하여 프로젝트 구성 요소에 대한 정보를 정의하고 집계할 수 있습니다.
- Kritis는 아티팩트(컨테이너 이미지)가 중앙 정책을 준수하고 선택적으로 필요한 증명이 존재하지 않는 한 배포가 방지되도록 API를 정의합니다.
https://cloud.google.com/binary-authorization?hl=ko
https://github.com/grafeas/kritis
간소화된 파이프라인
- 컨테이너를 생성하기 위한 소스 코드는 소스 제어에 저장됩니다.
- 소스 제어에 대한 변경 사항을 커밋하면 컨테이너가 빌드되고 테스트됩니다.
- 빌드 및 테스트 단계가 완료되면 컨테이너 이미지 아티팩트가 배포 준비가 된 중앙 컨테이너 레지스트리에 배치됩니다.
- 해당 컨테이너 버전의 배포가 Kubernetes API에 제출되면 컨테이너 런타임은 컨테이너 레지스트리에서 해당 컨테이너 이미지를 가져와 포드로 실행합니다.
컨테이너 빌드 파이프라인에는 각 단계가 성공적으로 완료되었음을 나타내거나 "증명"하기 위해 추가 프로세스를 주입할 수 있는 기회가 있습니다. 예를 들어 단위 테스트 실행, 소스 제어 분석 확인, 라이선스 확인, 취약성 분석 등이 있습니다. 각 단계에는 완료되는 해당 단계에 서명할 수 있는 권한 또는 "증명 권한"이 부여될 수 있습니다. "증명 기관"은 올바른 PGP 키와 해당 "증명"을 Container Analysis API에 등록할 수 있는 능력이 있는 사람 또는 시스템입니다.
각 단계에 대해 별도의 PGP 키를 사용하여 각 증명 단계는 파이프라인(a)의 다른 사람, 시스템 또는 빌드 단계에서 수행할 수 있습니다. 각 PGP 키는 컨테이너 분석 API에 저장되는 "증명 메모"와 연결됩니다. 빌드 단계에서 이미지에 "서명"하면 해당 이미지에 대한 JSON 메타데이터 스니펫이 PGP를 통해 서명되고 해당 서명된 스니펫이 API에 "메모 항목"으로 제출됩니다.
(b).컨테이너 이미지가 구축되고 필요한 증명이 중앙에 저장되면 정책 결정 프로세스의 일부로 쿼리할 수 있습니다. 이 경우 Kubernetes Acceptance Controller 는 Pod 에 대한 API 요청을 수신할 때 create 다음 update을 수행합니다.
- 정책 결정을 위해 WebHook을 Binary Authorization API로 보냅니다.
- 그런 다음 Binary Authorization 정책을 참조합니다.
- 필요한 경우 필요한 증명 발생에 대해 컨테이너 분석 API도 쿼리됩니다.
- 컨테이너 이미지가 정책을 준수하면 실행할 수 있습니다.
- 컨테이너 이미지가 정책을 충족하지 못하면 차단된 이유를 설명하는 메시지와 함께 API 클라이언트에 오류가 표시됩니다.
1. 실습에 필요한 환경 세팅
필요한 리소스 복제합니다.
gsutil -m cp -r gs://spls/gke-binary-auth/* .
cd gke-binary-auth-demo
지역 및 영역 설정 합니다.
gcloud config set compute/region us-central1
gcloud config set compute/zone us-central1-a
파일 권한 업데이트 합니다.
chmod +x create.sh
chmod +x delete.sh
chmod 777 validate.sh
2. Cluster Version 설정
create.sh의 GKE_VERSION 변수를 defaultClusterVersion 값으로 설정합니다.
sed -i 's/validMasterVersions\[0\]/defaultClusterVersion/g' ./create.sh
3. 배포
./create.sh -c my-cluster-1
Enabling the API compute.googleapis.com
Enabling the API container.googleapis.com
Enabling the API containerregistry.googleapis.com
Enabling the API containeranalysis.googleapis.com
Enabling the API binaryauthorization.googleapis.com
및 GKE 생성합니다.
4. Binary Authorization 정책설정
Console 접근합니다.
Edit Policy 클릭합니다.
현재 기본규칙으로 Allow all Images으로 되어 있으며 이는 클러스터에서 Binary Authorization이 사용 설정되지 않은 것처럼 동작을 모방합니다.
Custom exemption rules을 통해 Image Pattern을 추가할 수 있습니다.
기본 규칙이 Disallow all images또는 로 변경 Allow only images that have been approved by all of the following attestors되면 면제된 레지스트리 이미지 경로와 일치하지 않거나 필요한 증명이 없는 이미지가 각각 차단됩니다
기본 규칙을 Disallow all images 변경합니다.
추가로 GKE Cluster 설정을 합니다.
정책을 저장 합니다.
5. Pirvate GCR Image 생성
gcr.io/google-containers/nginx에 있는 nginx를 Pull 한 후 프로젝트의 Private GCR에 Push 합니다.
docker pull gcr.io/google-containers/nginx:latest
gcloud auth configure-docker
PROJECT_ID="$(gcloud config get-value project)"
docker tag gcr.io/google-containers/nginx "gcr.io/${PROJECT_ID}/nginx:latest"
docker push "gcr.io/${PROJECT_ID}/nginx:latest"
gcloud container images list-tags "gcr.io/${PROJECT_ID}/nginx"
6. 모든 이미지 거부 Test
Private GCR을 이용하여 Nginx 배포 테스트 합니다.
cat << EOF | kubectl create -f -
apiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
containers:
- name: nginx
image: "gcr.io/${PROJECT_ID}/nginx:latest"
ports:
- containerPort: 80
EOF
### Result
pod/nginx created
NAME READY STATUS RESTARTS AGE
nginx 1/1 Running 0 41s
다시 삭제합니다.
kubectl delete pod nginx
Binary Authorization 에서 Secific rules 에서 설정한 GKE의 옵션을 Disallow all images 변경합니다.
다시 동일하게 Nginx 를 배포하게 되면 API 서버 정책으로 인해 실패하게 된다.
cat << EOF | kubectl create -f -
apiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
containers:
- name: nginx
image: "gcr.io/${PROJECT_ID}/nginx:latest"
ports:
- containerPort: 80
EOF
### Result
Error from server (VIOLATES_POLICY): error when creating "STDIN": admission webhook "imagepolicywebhook.image-policy.k8s.io" denied the request: Image gcr.io//nginx:latest denied by Binary Authorization cluster admission rule for us-central1-a.my-cluster-1. Denied by always_deny admission rule
GKE Audit Log를 통해 모든 이미지가 차단됐는지 Cloud Logging 에서 확인합니다.
검색 쿼리 입력 및 차단된 로그를 확인합니다.
resource.type="k8s_cluster" protoPayload.response.reason="VIOLATES_POLICY"
7. 허용 목록에 있는 Cotainer Registry를 제외한 Image 거부
Binary Authorization에서 다시 수정한다.
Image exempt from this policy 부분에 gcr image 경로를 추가한다.
GCR의 nginx를 배포하게 되면 정상적으로 배포가 된다.
cat << EOF | kubectl create -f -
apiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
containers:
- name: nginx
image: "gcr.io/qwiklabs-gcp-04-099a182c7efc/nginx:latest"
ports:
- containerPort: 80
EOF
### Result
pod/nginx created
테스트 완료 하였으면 다시 Pod 삭제
kubectl delete pod nginx
8. Enforcing attestations
GCR 허용 목록을 등록 함으로써 원하지 않는 컨테이너 이미지가 클러스터에 배포되는 것을 방지하는 첫번째 단계 이지만 컨테이너가 올바르게 배포 되었는지 확인하기 위해 더많은 작업이 필요합니다.
지정된 컨테이너 이미지가 배포용으로 승인되었는지 암호화 방식으로 확인하려고 합니다.이것은 특정 단계가 완료되었다는 사실을 진술하거나 증명 하는 "증명 기관"에 의해 수행됩니다 . 증명 기관은 PGP 키를 사용하여 컨테이너 이미지의 SHA256 해시를 설명하는 메타데이터 스니펫에 서명 하고 이를 중앙 메타데이터 저장소인 컨테이너 분석 API에 제출함으로써 이를 수행합니다.
나중에 허용 컨트롤러가 이미지에 증명이 있어야 하는 Binary Authorization 정책을 참조하여 컨테이너 이미지 실행이 허용되는지 검증할 때 Container Analysis API가 서명된 스니펫을 보유하고 있는지 확인합니다. 어떤 단계가 완료되었는지 알려주는 메타데이터. 해당 정보를 통해 승인 컨트롤러는 해당 포드의 실행을 허용할지 거부할지 여부를 알 수 있습니다.
다음으로 컨테이너 이미지의 수동 증명을 수행합니다. 인간 증명 기관의 역할을 맡아 컨테이너 이미지에 서명하는 모든 단계를 수행하고 클러스터 내에서 실행 중인 이미지에 해당 증명이 있어야 하는 정책을 생성한 다음 포드에서 해당 이미지를 성공적으로 실행합니다.
필요한 변수설정
### 증명인 이름/세부정보
ATTESTOR="manually-verified" # No spaces allowed
ATTESTOR_NAME="Manual Attestor"
ATTESTOR_EMAIL="$(gcloud config get-value core/account)" # This uses your current user/email
### 증명 기관의 컨테이너 분석 메모 ID/설명
NOTE_ID="Human-Attestor-Note" # No spaces
NOTE_DESC="Human Attestation Note Demo"
### PayLoad/Requests을 생성하기 위한 파일 이름
NOTE_PAYLOAD_PATH="note_payload.json"
IAM_REQUEST_JSON="iam_request.json"
증명 노트 만들기
첫번째 단계는 컨테이너 분석 API를 통해 컨테이너 분석 노트(https://cloud.google.com/binary-authorization/docs/key-concepts?hl=ko#analysis_notes)로 증명 권한을 등록하는 것입니다.이렇게 하려면 ATTESTATION NOTE를 작성하여 API를 호출합니다.
ATTESTATION Note payload 생성
cat > ${NOTE_PAYLOAD_PATH} << EOF
{
"name": "projects/${PROJECT_ID}/notes/${NOTE_ID}",
"attestation_authority": {
"hint": {
"human_readable_name": "${NOTE_DESC}"
}
}
}
EOF
ATTESTATION Container Analysis API NOTE 제출
curl -X POST \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $(gcloud auth print-access-token)" \
--data-binary @${NOTE_PAYLOAD_PATH} \
"https://containeranalysis.googleapis.com/v1beta1/projects/${PROJECT_ID}/notes/?noteId=${NOTE_ID}"
### Result
{
"name": "projects/qwiklabs-gcp-04-099a182c7efc/notes/Human-Attestor-Note",
"kind": "ATTESTATION",
"createTime": "2023-01-30T08:51:20.454075Z",
"updateTime": "2023-01-30T08:51:20.454075Z",
"attestationAuthority": {
"hint": {
"humanReadableName": "Human Attestation Note Demo"
}
}
PGP 서명 키 생성
증명 기관이 PGP 키를 사용하여 이미지 메타데이터의 암호화 서명을 수행하므로 새 PGP 키를 생성하고 공개 PGP 키를 내보냅니다.
*** 이 오류 메시지는 이 활동과 관련이 있습니다. PGP 키는 이 연습을 위해 암호로 보호되지 않습니다. 프로덕션 시스템에서는 개인 PGP 키를 적절하게 보호해야 합니다.
### 변수선언
PGP_PUB_KEY="generated-key.pgp"
### PGP 키 생성
sudo apt-get install rng-tools
sudo rngd -r /dev/urandom
gpg --quick-generate-key --yes ${ATTESTOR_EMAIL}
(무슨 창 나오면 엔터)
### 공개 PGP 키 추출
gpg --armor --export "${ATTESTOR_EMAIL}" > ${PGP_PUB_KEY}
Binary Authorization API에 증명자 등록
다음 단계는 Binary Authorization API에서 "증명자"를 만들고 여기에 공개 PGP 키를 추가하는 것입니다.
### Binary Authorization API에서 증명자 생성
gcloud --project="${PROJECT_ID}" \
beta container binauthz attestors create "${ATTESTOR}" \
--attestation-authority-note="${NOTE_ID}" \
--attestation-authority-note-project="${PROJECT_ID}"
### 증명자에 PGP 키 추가
gcloud --project="${PROJECT_ID}" \
beta container binauthz attestors public-keys add \
--attestor="${ATTESTOR}" \
--pgp-public-key-file="${PGP_PUB_KEY}"
### 새로 생성된 증명자 나열
gcloud --project="${PROJECT_ID}" \
beta container binauthz attestors list
### Result
NAME: manually-verified
NOTE: projects/qwiklabs-gcp-04-099a182c7efc/notes/Human-Attestor-Note
NUM_PUBLIC_KEYS: 1
9. 컨테이너 이미지 서명
지금까지 작업은 단 한번만 하면 되지만 모든 새 컨테이너 이미지는 아래 방법을 반복해야합니다.
nginx이미지 는 gcr.io/google-containers/nginx:latest이미 빌드되어 사용할 수 있습니다. 자체 프로세스로 빌드한 자체 이미지인 것처럼 수동 증명을 수행하고 빌드해야 하는 단계를 저장합니다.
### 변수선언
GENERATED_PAYLOAD="generated_payload.json"
GENERATED_SIGNATURE="generated_signature.pgp"
### PGP 지문 가져오기
PGP_FINGERPRINT="$(gpg --list-keys ${ATTESTOR_EMAIL} | head -2 | tail -1 | awk '{print $1}')"
### 컨테이너 이미지의 SHA256 가져오기
IMAGE_PATH="gcr.io/${PROJECT_ID}/nginx"
IMAGE_DIGEST="$(gcloud container images list-tags --format='get(digest)' $IMAGE_PATH | head -1)"
### Json 형식의 서명 가져오기
gcloud beta container binauthz create-signature-payload \
--artifact-url="${IMAGE_PATH}@${IMAGE_DIGEST}" > ${GENERATED_PAYLOAD}
### PGP키로 PayLoad 서명
gpg --local-user "${ATTESTOR_EMAIL}" \
--armor \
--output ${GENERATED_SIGNATURE} \
--sign ${GENERATED_PAYLOAD}
### 증명 생성
gcloud beta container binauthz attestations create \
--artifact-url="${IMAGE_PATH}@${IMAGE_DIGEST}" \
--attestor="projects/${PROJECT_ID}/attestors/${ATTESTOR}" \
--signature-file=${GENERATED_SIGNATURE} \
--public-key-id="${PGP_FINGERPRINT}"
### 새로 생성된 증명 확인
gcloud beta container binauthz attestations list \
--attestor="projects/${PROJECT_ID}/attestors/${ATTESTOR}"
### Result
---
attestation:
serializedPayload: ewogICJjcml0aWNhbCI6IHsKICAgICJpZGVudGl0eSI6IHsKICAgICAgImRvY2tlci1yZWZlcmVuY2UiOiAiZ2NyLmlvL3F3aWtsYWJzLWdjcC0wNC0wOTlhMTgyYzdlZmMvbmdpbngiCiAgICB9LAogICAgImltYWdlIjogewogICAgICAiZG9ja2VyLW1hbmlmZXN0LWRpZ2VzdCI6ICJzaGEyNTY6MDIxMTViYzc0MDlkYzA0NGY3MDBhNzI5Y2FjYzE2NDhkYzRlNDQ1NTcyZTU3NjVmYmNjNzcwMjliMGFkYmE2NyIKICAgIH0sCiAgICAidHlwZSI6ICJHb29nbGUgY2xvdWQgYmluYXV0aHogY29udGFpbmVyIHNpZ25hdHVyZSIKICB9Cn0K
signatures:
- publicKeyId: 42782C996DB641F8EC2852661747899F2FDE248B
signature: LS0tLS1CRUdJTiBQR1AgTUVTU0FHRS0tLS0tCgpvd0did012TXdNVW83dDQ1WC8rZVNqZmpXa2J2SkxIMDFMelVvc1NTMUpUNGdzVEtuUHpFRkwyczR2eTg1T3Z0ClBOVmNDZ3BLeVVXWkpabkppVGxLVmdvZ1BsQWtNeVUxcnlTenBCSXVBaFJMeVUvT1RpM1NMVXBOU3kxS3pVdE8KQmNvcHBTY1g2V1htNnhlV1oyYm5KQ1lWNjZZbkYrZ2FtT2dhV0ZvbUdsb1lKWnVucGlYcjU2Vm41bFVvZ1UycAoxWUVhbjV1WW5vckY3TnpFdk15MDFPSVMzWlRNZENBRnNxRTRJOUhJMU16S3dNalEwRFFwMmR6RXdESWwyY0RFCkpNM2N3Q0RSM01neU9URTUyZERNeENJbDJTVFZ4TVRVMU53bzFkVGN6RFF0S1RuWjNOekF5RExKSURFbEtkSE0KSE5YK2tzb0NzUFBkOC9QVGMxSVZrblB5UzFNVWtqTHpFa3RMTXFvVWt2UHpTaEl6Z1NHbVVKeVpucGRZVWxxVQpDdEpleTFYTDFjbDRrNFdCa1l2QldVeVJ4YWxDWjJidU5zY2ZielNDMG1EQnpjb0VDbFJWbWVLU1VsQUk2aG9ZCjZGb2tXYVFsbXBpWXBCaVlHanJBQWtvdkw3V0VnWXRUQUtaUDVTRDNYMW5tcVpYcEZobkZoNzQ3bnMzTldMdnkKYWZTWmZ3dkRnODd6L01rK3M4TEpQdDY3NnZiU0JTV2lLNm8vV2YrcmRCSVdUaXN6MUJJem12TFJUR3ZkYVoyYQpubm5tWFNZRmVjbjJPMllXTEc2MUtPK3ZNZHZBbFhCQVN1UElqS3E5RTNmTkRXU3B1enE1OXhmYnhtTld3Y3RzClg0a0hjOWYwdU8rKzhPQ3Bra044anRudnRBZVAvQnYzenI0ajByUi9WOGJpL3drZDIxdDRuNmRmWG1ueTdJaW8KMU9QdDdHdDNUSGhxWmZmYk5sZmV2RlFwMHZ1TTNqR2I5eFlwZTQ3YWYyanN6VGZNaVJZMll5MWE5TnJDSytsTQo1cFZETmdkS05oNjJQUmxvcHBKMDhRemZFZFYvcnBiN3J4dEZmOWI4RXl5a3l0TWhhR0RndkdYWFI5K05wKzdlCm1lTjJ6c1ArN3RrZHRUYWxNOExDRmxuTTZONWZicXBnYTV2Sk4zV2VwZGEvc3kzVldmdHZ6TnRYTW50SHcwcTEKSTI1UE9uZGZkYjljVnQrK0piWjJtZXlaeFo0TzdMdGEzeGZIWjkxU0RxLy83S2g1MFRnMlFINVJWTW82MlEzdgp2Nnk2Y0wwcWxIZWE4TzdEUGhubHMyYjJQOTRsdmVWbi9LL2NHb3U1U3dvbVgzdi8va3JGbEduWHRuNFVYSnJ6CnU3RHFJd0E9Cj1YNUkzCi0tLS0tRU5EIFBHUCBNRVNTQUdFLS0tLS0K
createTime: '2023-01-30T09:00:05.263811Z'
kind: ATTESTATION
name: projects/qwiklabs-gcp-04-099a182c7efc/occurrences/67399e1c-10c5-4eaa-ab42-6d7d7f90afba
noteName: projects/qwiklabs-gcp-04-099a182c7efc/notes/Human-Attestor-Note
resourceUri: https://gcr.io/qwiklabs-gcp-04-099a182c7efc/nginx@sha256:02115bc7409dc044f700a729cacc1648dc4e445572e5765fbcc77029b0adba67
updateTime: '2023-01-30T09:00:05.263811Z'
10. 증명 적용 활성화 된 이미지 실행
### 증명이 필요하도록 정책을 변경하려면 다음을 실행한 다음 증명 기관의 전체 경로/이름을 복사합니다.
echo "projects/${PROJECT_ID}/attestors/${ATTESTOR}" # Copy this output to your copy/paste buffer
Binary Authorization 에서 GKE 정책 수정
기존에 설정한 Custom exemption rule 삭제
GKE Cluster rule edit
Attestors 부분에서 projects/${PROJECT_ID}/attestors/${ATTESTOR} 입력합니다.
### 이전 단계에서 서명된 이미지의 가장 최근 SHA256를 가져옵니다.
IMAGE_PATH="gcr.io/${PROJECT_ID}/nginx"
IMAGE_DIGEST="$(gcloud container images list-tags --format='get(digest)' $IMAGE_PATH | head -1)"
자격 증명으로 GCR에서 Nginx 배포 되는지 확인합니다.
cat << EOF | kubectl create -f -
apiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
containers:
- name: nginx
image: "${IMAGE_PATH}@${IMAGE_DIGEST}"
ports:
- containerPort: 80
EOF
### Result
pod/nginx created
출처
GCP Skillboost - Google Kubernetes Engine Security: Binary Authorization
https://partner.cloudskillsboost.google/focuses/14045?parent=catalog
'Cloud > GCP' 카테고리의 다른 글
[GCP] Cloud Build 를 이용한 GKE 파이프라인 구축 (0) | 2023.02.14 |
---|---|
[GCP] Cloud Code - VS Code 환경 구축 (0) | 2023.02.10 |
[GCP] Apigee, Cloud Armor 이용하여 API 보호 (0) | 2023.01.17 |
[GCP] NAT를 이용하여 Apigee Backend Service (0) | 2023.01.17 |
[GCP] Apigee X Environment 및 Group 추가 설정 (0) | 2023.01.17 |