GCP의 서비스 간 인증을 위해 Go 사용
개시하다
클라우드 런은 편하죠.컨테이너를 조정하기만 하면 마음대로 축소할 수 있고, 사용하지 않으면 제로 실례에서 할 수 있다.이러한 처리의 편리함 외에'좋다'와 개인의 기능 중 하나는 계정 수준의 접근 제어이다.
Authentication을 "인증되지 않은 라이센스"로 설정하면 누구나 사용할 수 있는 공개 API 상태가 되며, ACL을 설정하면 서비스 계정이나 사용자 계정 단위로 실행되는 사용자나 애플리케이션을 제한할 수 있습니다.이런 안전성은 응용 프로그램 없이도 쉽게 만들 수 있다.
이번에는 어떻게 아이덴티티톡을 꺼냅니까?"인증 방법", "ID 영패를 얻는 방법", 나는 겸사겸사 필기를 하고 싶다.
또 전반부는 조사 결과가 필요 없기 때문에 결론을 알고 싶은 사람은 갈 수 있다아랫부분.
TL;DR
서비스 간 인증 개요
아무튼 서비스 간 인증부터 살짝 확인해 볼게요.공식 수첩이 여기 있습니다.
기본적으로 OAuth ID 토큰을 통해 인증됩니다.따라서 어떤 인증 정보도 어기지 않고 던지면 다음과 같은 오류가 발생할 수 있다.
❯ curl -i https://target-xxx.a.run.app/healthcheck
HTTP/2 403
date: Thu, 17 Jun 2021 14:48:11 GMT
content-type: text/html; charset=UTF-8
server: Google Frontend
content-length: 306
alt-svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000,h3-T051=":443"; ma=2592000,h3-Q050=":443"; ma=2592000,h3-Q046=":443"; ma=2592000,h3-Q043=":443"; ma=2592000,quic=":443"; ma=2592000; v="46,43"
<html><head>
<meta http-equiv="content-type" content="text/html;charset=utf-8">
<title>403 Forbidden</title>
</head>
<body text=#000000 bgcolor=#ffffff>
<h1>Error: Forbidden</h1>
<h2>Your client does not have permission to get URL <code>/healthcheck</code> from this server.</h2>
<h2></h2>
</body></html>
재미있는 것은 오류에 대한 응답이 Google Fronted라는 것입니다.이때는 앱에도 요청하지 않았다는 것이다.그래서 일지를 볼 때 주의하세요.이렇게 슬라이드카처럼 다양한 일을 하는 것이 좋다.그렇다면 어떻게 하면 성공할 수 있을까? 오옥스 아이디 영패만 건네주면 되기 때문이다
gcloud auth print-identity-token
. 사용하기가 간단하다.이것은 Authorization
그것을 눈썹에 넣는 흔한 방법이다.❯ curl -v -H "Authorization: Bearer $(gcloud auth print-identity-token)" https://target-xxx.a.run.app/healthcheck
- 中略
* Using HTTP2, server supports multi-use
* Connection state changed (HTTP/2 confirmed)
* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0
* Using Stream ID: 1 (easy handle 0x558429803820)
> GET /healthcheck HTTP/2
> Host: target-xxx.a.run.app
> user-agent: curl/7.68.0
> accept: */*
> authorization: Bearer xxxxx
- 中略
{"message":"OK","date":"2021-06-17T00:00:00"}
요청이 성공했습니다.간단하죠?그럼 고로 이거 한번 해보자...그나저나 Go의 프로젝트에서 얻는 방법
identity-token
을 몰라서 길을 좀 잃었어요.아까 공식 매뉴얼을 봐도 메타데이터에서 얻은 거죠?
tokenURL := fmt.Sprintf("/instance/service-accounts/default/identity?audience=%s", serviceURL)
idToken, err := metadata.Get(tokenURL)
이건 이렇게 하면 되지만 현지에서 이동이 안 돼서 힘들어요.나는 ADC를 활용하고 싶어서 스스로 조립하는 방향을 고려했다.여기는'어?'그렇게 생각하는 사람.아마 너는 옳다.그래서 너는 세 가지 선택이 있어.
a) 계속 읽기: 다음
b) 결론만 바라기: 전진idtoken
c) 됐습니다: 브라우저를 가볍게 닫습니다.
ADC
GCP의 많은 프로그램 라이브러리는 응용 프로그램에 대응한다기본 인증 정보(ADC/Application Default Credentials).다음 순서에 따라 인증 정보를 확인하고 기본 인증을 합니다.
GOOGLE_APPLICATION_CREDENTIALS
을 미리 가져와 경로를 넣는 흔한 방법입니다.3GCP 내 운영 환경에서 사용할 수 있습니다.Compute Engine, Google Kubernetes Engine, App Engine, Cloud Run, Cloud Function 등에 해당하는기본 계정 또는 사용자가 지정한 서비스 계정을 사용할 수 있습니다.이것은 서버의 로컬과 이미지 내부에 계정 인증 정보를 가지고 있을 필요가 없기 때문에 매우 편리하다.
다만, 뒷면에서 ADC를 사용하는 프로그램 라이브러리는 있지만, 매뉴얼에서 ADC를 직접 사용하는 방법을 찾지 못했다.어쩔 수 없었기 때문에 Google의 라이브러리
GOOGLE_APPLICATION_CREDENTIALS
에서 사용한 곳을 찾아보니 다음과 같은 코드가 발견되었습니다.// FindDefaultCredentialsWithParams searches for "Application Default Credentials".
//
// It looks for credentials in the following places,
// preferring the first location found:
//
// 1. A JSON file whose path is specified by the
// GOOGLE_APPLICATION_CREDENTIALS environment variable.
// For workload identity federation, refer to
// https://cloud.google.com/iam/docs/how-to#using-workload-identity-federation on
// how to generate the JSON configuration file for on-prem/non-Google cloud
// platforms.
// 2. A JSON file in a location known to the gcloud command-line tool.
// On Windows, this is %APPDATA%/gcloud/application_default_credentials.json.
// On other systems, $HOME/.config/gcloud/application_default_credentials.json.
// 3. On Google App Engine standard first generation runtimes (<= Go 1.9) it uses
// the appengine.AccessToken function.
// 4. On Google Compute Engine, Google App Engine standard second generation runtimes
// (>= Go 1.11), and Google App Engine flexible environment, it fetches
// credentials from the metadata server.
func FindDefaultCredentialsWithParams(ctx context.Context, params CredentialsParams) (*Credentials, error) {
구체적인 사용 방법은 다음과 같다.간단하네.// ADC
credentials, err := google.FindDefaultCredentials(oauth2.NoContext, AuthUrl)
if err != nil {
fmt.Println(err)
}
config, err := google.JWTConfigFromJSON(credentials.JSON)
if err != nil {
fmt.Println(err)
}
하지만 본 GCS의 라이브러리 등에 액세스 토큰이 생성돼 아이디 토큰을 직접 만들 수 없어 검색이 계속되고 있다.ID 토큰 생성
우선 아래 두 가지를 참고하여 저는 ID 영패를 인증하기 위해 필요하지 않은 투척 코드를 썼습니다.특히 두 번째 링크는 바로 제가 하고 싶은 일이어서 참고 가치가 있습니다.
상술한 내용을 참고하여 만든 코드는 다음과 같다.
func main() {
url := "https://target-xxx.a.run.app/healthcheck"
token := google.IdToken(url)
client := &http.Client{}
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", token))
res, _ := client.Do(req)
body, _ := io.ReadAll(res.Body)
defer res.Body.Close()
fmt.Println(string(body))
}
영패의 생성 자체는 이쪽 모듈과 분리된다.const AuthUrl = "https://www.googleapis.com/oauth2/v4/token"
func IdToken(targetAudience string) string {
// ADC
credentials, err := google.FindDefaultCredentials(oauth2.NoContext, AuthUrl)
if err != nil {
fmt.Println(err)
}
config, err := google.JWTConfigFromJSON(credentials.JSON)
if err != nil {
fmt.Println(err)
}
// JWS
iat := time.Now()
exp := iat.Add(time.Hour)
cs := &jws.ClaimSet{
Iss: config.Email,
Sub: config.Email,
Aud: AuthUrl,
Iat: iat.Unix(),
Exp: exp.Unix(),
}
hdr := &jws.Header{
Algorithm: "RS256",
Typ: "JWT",
KeyID: config.PrivateKeyID,
}
privateKey := ParseKey(config.PrivateKey)
// Request Google OAuth server to get Token ID
cs.PrivateClaims = map[string]interface{}{"target_audience": targetAudience}
msg, err := jws.Encode(hdr, cs, privateKey)
if err != nil {
fmt.Println(fmt.Errorf("google: could not encode JWT: %v", err))
}
f := url.Values{
"grant_type": {"urn:ietf:params:oauth:grant-type:jwt-bearer"},
"assertion": {msg},
}
res, err := http.PostForm(AuthUrl, f)
if err != nil {
fmt.Println(err)
}
body, err := io.ReadAll(res.Body)
defer res.Body.Close()
if err != nil {
fmt.Println(err)
}
type resIdToken struct {
IdToken string `json:"id_token"`
}
id := &resIdToken{}
json.Unmarshal(body, id)
return id.IdToken
}
func ParseKey(key []byte) *rsa.PrivateKey {
block, _ := pem.Decode(key)
if block != nil {
key = block.Bytes
}
parsedKey, err := x509.ParsePKCS8PrivateKey(key)
if err != nil {
parsedKey, err = x509.ParsePKCS1PrivateKey(key)
if err != nil {
return nil
}
}
parsed, ok := parsedKey.(*rsa.PrivateKey)
if !ok {
return nil
}
return parsed
}
포인트는 target_audience
PrivateClaims 같은 데 맡기는 거예요.나는 어떻게 너에게 줄지 몰라서 매우 괴롭다.마지막으로 GiitHub의 코드를 읽었는데 "쿠루토 카페"같은 느낌이 들었다.실행하면 서버의 반환 값을 아래와 같이 안전하게 표시할 수 있습니다.
❯ go run main.go
{"message":"OK","date":"2021-06-18T00:00:00"}
google.golang.org/api/idtoken
그럼, "내가 하고 싶은 일을 했어!"이렇게 좋은 마음으로 다음 문서를 다시 봤다.
GCP 외부에서 호출된 엉망진창인 기록이 있어요!왜 처음부터 그걸 안 읽었을까...
따라서 참고Identity-Aware Proxy 샘플 코드사용
idtoken.NewClient
의 작법은 다음과 같다.func main() {
url := "https://target-xxx.a.run.app/healthcheck"
ctx := context.Background()
client, _ := idtoken.NewClient(ctx, url)
req, _ := http.NewRequest("GET", url, nil)
res, _ := client.Do(req)
body, _ := io.ReadAll(res.Body)
defer res.Body.Close()
fmt.Println(string(body))
}
상당히 시원하네요!Authorization: Bearer
머리글이 작성되지 않았지만 자동으로 추가되었습니다.실행 결과는 다음과 같다.❯ go run main.go
{"message":"OK","date":"2021-06-18T00:00:00"}
결과는 동일합니다!총결산
Go에서 GCP 클라우드 런에서 서비스 간 인증을 받은 Identity Token은 어떻게 취득합니까?를 입력합니다.언어가 바뀌어도 기본적으로 같은 생각으로 사용해야 한다.
하지만 부인할 수 없는 것은 처음 문서를 볼 때 잠이 혼미해져서 중요한 곳을 건너뛰고 시행착오를 많이 겪었기 때문이다...단순히 아이덴티티톡의 API를 취할 준비가 되지 않아 여러 바퀴를 돌았다.적어도 중간에 다시 읽으면 orz도 괜찮아요. 결과적으로 내용은 이미 알고 있으니까 괜찮은 것 같아요.
그럼 해피 해킹!
Reference
이 문제에 관하여(GCP의 서비스 간 인증을 위해 Go 사용), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://zenn.dev/koduki/articles/3c1bc44efbbaf1텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)