[GCP]TLS 와 IAM Auth 가 적용된 Memorystore for Redis Cluster 에 연결하기

이정운 (Jungwoon Lee)
google-cloud-apac
Published in
20 min readMay 7, 2024

안녕하세요 이정운 입니다.

우연한 기회에 Memorystore for Redis Cluster 에 대해서 간단하게 접속 테스트 해볼 일이 있었는데 생각보다 샘플 소스나 가이드가 적어서 테스트하는데 좀 고생을 했었습니다. 특히, Memorystore for Redis Cluster 에 추가 설정이 없는 경우에는 기존에 있는 라이브러리들을 활용하거나 일반적인 방식으로 연결 및 호출하면 되기때문에 큰 이슈가 없습니다. 그러나 강화된 보안 관점에서 TLS 와 IAM Auth 를 적용한 상태에서는 기존의 user id, password 를 사용하는 방식이 아니라 Google cloud 의 IAM 을 통해서 권한이 있는 사용자의 credential 을 가져오는 부분이 추가되어야 하는데 생각보다 해당 예제가 없어서 간단하게 직접 애플리케이션을 작성해서 테스트해보고 참고하실만한 기본 가이드를 작성해봅니다.

Memorystore for Redis Cluster overview
https://cloud.google.com/memorystore/docs/cluster/memorystore-for-redis-cluster-overview

저만의 생각일 수도 있지만 요즘 Cloud 에서 동작하는 DB 들은 대부분 이전 방식의 user id, password 보다 각 Cloud 업체에서 제공하는 IAM 을 연동하여 IAM Auth 로 권한 관리를 중앙에서 제공하는 형태가 많아지는 것으로 보입니다. 따라서, 향후에는 좀 더 다양한 예제들이 제공되지 않을까 기대해 봅니다.

About IAM authentication
https://cloud.google.com/memorystore/docs/cluster/about-iam-auth

#0) Memorystore for Redis cluster 준비

우선 테스트를 위해서 가장먼저 Memorystore for Redis cluster 가 필요합니다.

Memorystore for Redis Cluster overview
https://cloud.google.com/memorystore/docs/cluster/memorystore-for-redis-cluster-overview

본 이야기에서는 하단과 같이 TLS 와 IAM Auth 가 적용된 Memorystore for Redis Cluster 가 만들어졌다고 가정하고 진행하도록 하겠습니다. (추가적으로 Private Service Connect(PSC)를 사용했는데 이건 요구 환경에따라 선택하시면 될듯 합니다.)

혹시, Memorystore for Redis cluster 를 생성해야 한다면 하단의 가이드를 참고하셔서 생성하시면 됩니다.

Create instances
https://cloud.google.com/memorystore/docs/cluster/create-instances

#2) Client 역할을 담당할 GCE or GKE 에 대한 준비

본 이야기에서 또하나의 가정은 Google cloud 환경 안의 GCE(VM) 에서 Memorystore for Redis cluster 가 호출된다고 가정하도록 하겠습니다. 다시 말씀드려서 GCE 가 client 역할을 수행하는 것이죠. 당연히 GCE 말고도 사용자 애플리케이션이 동작할 수 있는 GKE, Cloud Run 등에서도 가능하겠지만 테스트를 단순화하기 위해서 GCE 에서 진행하도록 하겠습니다.

Create and start a VM instance
https://cloud.google.com/compute/docs/instances/create-start-instance

준비된 GCE 가 없다면 상단의 가이드를 참고해서 GCE 를 하나 생성하면 됩니다. 다만, 하나 고려하실 부분은 생성시에 Access scopes 을 설정하는 부분이 나오는데 Memorystore for Redis cluster 호출을 허용하기 위해서 Cloud Platform API 에 대해서 access 를 허용해주어야 합니다.

기 준비된 GCE 인스턴스를 활용해서 Access scopes 만 변경하려면 하단의 가이드를 참고하시면 됩니다.

Attach the service account and update the access scope
https://cloud.google.com/compute/docs/instances/change-service-account#changeserviceaccountandscopes

이렇게 GCE 가 준비되었다면 TLS 통신을 위해서 Memorystore for Redis cluster 의 Certificate Authorities 파일(server-ca.pem)을 다운로드 받아서 준비된 GCE 로 옮겨 놓습니다. 참고로 Google cloud 의 관리콘솔에서 Memorystore for Redis cluster 화면으로 들어가시면 하단과 같이 CA 를 파일로 다운로드 받을 수 있습니다.

GCE 준비의 마지막 단계로 IAM Auth 연동을 위해서 GCE 가 사용하고 있는 Service account 에 Memorystore for Redis cluster 를 호출하기 위한 ‘roles/redis.dbConnectionUser’ 권한을 부여합니다.

Manage IAM authentication
https://cloud.google.com/memorystore/docs/cluster/manage-iam-auth

GCE 가 사용하고 있는 Service account 가 어떤것인지 확인을 원하시는 경우라면 관리콘솔에서 GCE 상세 내용으로 들어가면 하단과 같이 확인할 수 있습니다.(일반적인 보안 권장사항이지만 Compute engine default service account 를 사용하기 보다는 해당 목적에 맞게 별도의 service account 를 생성해서 사용하는 것을 권장드립니다.)

이렇게 하면 TLS 와 IAM Auth 가 enable 된 Memorystore for Redis Cluster 를 위한 client 준비는 완료된것입니다.

참고 #2.1) GKE 를 client 로 사용하는 경우에는 권한 부여를 위해서 Workload Identity Federation 을 활용하여 Kubernetes 의 service account 에 권한 부여가 가능합니다. 즉, Kubernetes 의 SA 에 ‘roles/redis.dbConnectionUser’ 권한을 직접 부여 가능합니다. 보다 권장되는 방식이므로 GKE 환경인 경우에는 하단의 가이드를 참고해서 Workload Identity Federation 을 활용하시기 바라겠습니다.

Configure applications to use Workload Identity Federation for GKE
https://cloud.google.com/kubernetes-engine/docs/how-to/workload-identity#authenticating_to

#3) TLS 와 IAM Auth 가 enable 된 Memorystore for Redis Cluster 에 연결해보기

Memorystore for Redis Cluster 와 client 역할을 수행할 GCE 준비가 완료되었으므로 간단하게 application 을 하나 작성해서 정상 접속 여부를 테스트 해보도록 하겠습니다.

Client library connection code samples
https://cloud.google.com/memorystore/docs/cluster/client-library-connection

상단의 가이드에 공식적인 샘플 소스도 있지만 Java 에서는 Redis 연결을 위해서 가장 많이 사용되는 오픈소스 중의 하나인 Lettuce 라이브러리(https://lettuce.io/)를 활용해서 간단하게 작성한 참고 소스입니다. 추가적으로 각자의 테스트를 위해서 변경되어야 할 부분은 하단에서 확인되는 것과 같이 Memorystore for Redis Cluster 의 Discovery Endpoint 의 IP 주소, 포트와 Certificate Authorities 파일(server-ca.pem)의 경로 입니다.

package com.example;

import java.io.File;
import java.io.IOException;
import java.security.GeneralSecurityException;

import com.google.auth.oauth2.AccessToken;
import com.google.auth.oauth2.GoogleCredentials;

import io.lettuce.core.RedisCredentials;
import io.lettuce.core.RedisCredentialsProvider;
import io.lettuce.core.RedisURI;
import io.lettuce.core.SslOptions;
import io.lettuce.core.cluster.ClusterClientOptions;
import io.lettuce.core.cluster.RedisClusterClient;
import io.lettuce.core.cluster.api.StatefulRedisClusterConnection;
import io.lettuce.core.cluster.api.sync.RedisAdvancedClusterCommands;
import reactor.core.publisher.Mono;

public class App
{
public static void main( String[] args ) throws GeneralSecurityException, IOException
{

String discoveryEndpointIp = "10.128.15.235";
int discoveryEndpointPort = 6379;

// for TLS communication, Load CA cert
SslOptions sslOptions = SslOptions.builder()
.jdkSslProvider()
.trustManager(new File("/home/admin/redis/maven-sample02/demo/server-ca.pem"))
.build();

ClusterClientOptions clientOptions = ClusterClientOptions.builder().sslOptions(sslOptions).build();

// Obtain credentials using Application Default Credentials
GoogleCredentials credentials = GoogleCredentials.getApplicationDefault();
AccessToken accessToken = credentials.refreshAccessToken();
System.out.println("accesstoken : " + accessToken.getTokenValue());

RedisCredentialsProvider provider = () -> Mono.just(RedisCredentials.just("default", accessToken.getTokenValue().toCharArray()));
RedisURI redisUri = RedisURI.Builder.redis(discoveryEndpointIp, discoveryEndpointPort).withSsl(true).withAuthentication(provider).build();

// Create Redis Cluster Client
RedisClusterClient clusterClient = RedisClusterClient.create(redisUri);
clusterClient.setOptions(clientOptions);

try (// Establish connection to Redis Cluster
StatefulRedisClusterConnection connection = clusterClient.connect()) {
// Retrieve synchronous Redis Cluster commands
RedisAdvancedClusterCommands syncCommands = connection.sync();

// Perform Redis operations
syncCommands.set("key", "Hello, Redis!");
System.out.println("key : " + syncCommands.get("key"));
// Close the connection and shutdown the client
connection.close();
}

clusterClient.shutdown();
}
}

해당 소스를 수행해보면 아시겠지만 하단과 같이 TLS 와 IAM Auth 가 적용된 Memorystore for Redis Cluster 에 정상적으로 접속이 가능하다는 것을 확인할 수 있습니다.

예제 소스를 작성한 김에 Java 만이 아니라 Go 의 go-redis 라이브러리 (https://github.com/redis/go-redis)를 활용해서도 유사하게 작성해서 테스트 해보도록 하겠습니다.

package main

import (
"context"
"crypto/tls"
"crypto/x509"
"io/ioutil"
"fmt"
"log"
"time"

"github.com/redis/go-redis/v9"
"golang.org/x/oauth2/google"
)

func main() {
// for TLS communication, Load CA cert
caFilePath := "./server-ca.pem"
caCert, err := ioutil.ReadFile(caFilePath)
if err != nil {
log.Fatal(err)
}
caCertPool := x509.NewCertPool()
caCertPool.AppendCertsFromPEM(caCert)

ctx := context.Background()

// Obtain credentials using Application Default Credentials
creds, err := google.FindDefaultCredentials(ctx, "https://www.googleapis.com/auth/cloud-platform")
if err != nil {
log.Fatalf("Failed to obtain credentials: %v", err)
}
ts := creds.TokenSource
token, err := ts.Token()
if err != nil {
log.Fatalf("Failed to get token: %v", err)
}
fmt.Println("AccessToken:", token.AccessToken)


// Setup Redis Connection pool
client := redis.NewClusterClient(&redis.ClusterOptions{
Addrs: []string{"10.128.15.235:6379"},
TLSConfig: &tls.Config{
RootCAs: caCertPool,
},
Username: "",
Password: token.AccessToken,
DialTimeout: 30 * time.Second,
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
ReadOnly: false,
RouteRandomly: false,
RouteByLatency: false,
})

err = client.Set(ctx, "key", "value", 0).Err()
if err != nil {
log.Fatal(err)
}
val, err := client.Get(ctx, "key").Result()
if err != nil {
log.Fatal(err)
}
fmt.Println("key", val)

}

당연히 Go 샘플도 문제없이 TLS 와 IAM Auth 가 적용된 Memorystore for Redis Cluster 에 정상적으로 접속이 가능하다는 것을 확인할 수 있습니다.

참고 #3.1) 가이드에서 사용한 테스트 소스는 하단의 github 에 올려놓았으니 참고하시기 바라겠습니다. 추가적으로 Java 의 경우에는 maven 사용하여 standalone 형태로 build 및 테스트했습니다.

memorystore-for-redis-cluster
https://github.com/jwlee98/memorystore-for-redis-cluster

참고 #3.2) 공식 가이드에 Code sample for both IAM auth and in-transit encryption이름으로 redis-py, Lettuce, Jedis, Go공식 샘플도 업데이트 되었으니 특히 운영 환경의 경우 해당 부분을 참고하시기 바라겠습니다.

Client library connection code samples
https://cloud.google.com/memorystore/docs/cluster/client-library-connection

참고 #3.3) 테스트 해보면서 다양한 에러를 겪었는데 해당 부분이 공유되면 도움이 될꺼 같아 간단하게 공유드립니다. 일반적으로 client 단에서 TLS 관련 부분이 미설정한 경우 “Error: Connection reset by peer” 형태의 에러가 발생하고 IAM Auth 관련 부분이 미설정된 경우에는 “NOAUTH Authentication required.” 라는 형태의 에러가 발생됩니다. 마지막으로 client 단에서 IAM Auth 를 설정했지만 Service account 매핑등에 이슈가 있어서 ‘roles/redis.dbConnectionUser’ 권한이 없는 경우에는 “WRONGPASS invalid username-password pair or user is disabled.” 형태의 에러가 발생됩니다.

지금까지 TLS 와 IAM Auth 가 적용된 Memorystore for Redis Cluster 에 연결하기 위해서 client 역할을 담당하는 GCE 환경 설정 및 Java, Go 로 샘플 애플리케이션을 작성해서 테스트 해봤습니다. 보시면 아시겠지만 크게 어려운 점은 없다는 것을 아실 수 있고 다만 IAM Auth 설정이 있는 경우 해당 권한을 획득하기 위해서 client code 에서 Google cloud 의 credentials 을 받아오기 위한 부분이 조금 더 추가되기만 하면 된다는 것을 이해했을 것이라고 판단됩니다.

그럼 여기까지해서 TLS 와 IAM Auth 가 적용된 Memorystore for Redis Cluster 에 연결하기 이야기는 마무리하고 다음에 다시 더 좋은 이야기로 돌아오도록 하겠습니다. ^^&

Disclaimer: 본 글의 작성자는 Google 직원이지만 Google cloud 를 공부하는 한 개인으로서 작성된 글입니다. 본 글의 내용, 입장은 Google 을 대변하지 않으며 Google 이 해당 콘텐츠를 보장하지 않습니다.

참고 자료 #1

Memorystore for Redis Cluster overview
https://cloud.google.com/memorystore/docs/cluster/memorystore-for-redis-cluster-overview

About IAM authentication
https://cloud.google.com/memorystore/docs/cluster/about-iam-auth

Create instances
https://cloud.google.com/memorystore/docs/cluster/create-instances

Create and start a VM instance
https://cloud.google.com/compute/docs/instances/create-start-instance

Attach the service account and update the access scope
https://cloud.google.com/compute/docs/instances/change-service-account#changeserviceaccountandscopes

Manage IAM authentication
https://cloud.google.com/memorystore/docs/cluster/manage-iam-auth

Configure applications to use Workload Identity Federation for GKE
https://cloud.google.com/kubernetes-engine/docs/how-to/workload-identity#authenticating_to

Client library connection code samples
https://cloud.google.com/memorystore/docs/cluster/client-library-connection

memorystore-for-redis-cluster
https://github.com/jwlee98/memorystore-for-redis-cluster

--

--

이정운 (Jungwoon Lee)
google-cloud-apac

Technical engineer who dreams better future. (These thoughts are my own personal opinions, and do not reflect or represent Google’s opinions or plans.)