Notice
Recent Posts
Recent Comments
Link
«   2025/04   »
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30
Tags
more
Archives
Today
Total
관리 메뉴

뭉크테크

AWS TechCamp2-1(Bedrock 서비스 tenant 인증 관리) 본문

AWS 아키텍쳐 구현 경험

AWS TechCamp2-1(Bedrock 서비스 tenant 인증 관리)

뭉크테크 2025. 3. 27. 22:37

https://catalog.us-east-1.prod.workshops.aws/workshops/e5ce2f2a-e576-41cc-838d-4b22da07c67d/ko-KR/1-introduction

 

Design Secure Bedrock SaaS Architecture with API gateway

Building a scalable and efficient Software as a Service (SaaS) architecture is critical for delivering robust cloud-based applications. This workshop will guide participants through the process of creating a Bedrock SaaS architecture, leveraging API Gatewa

catalog.us-east-1.prod.workshops.aws

 

개요

 

이번 시간은 AWS bedrock 서비스를 이용할 때, 사용자 인증과 모니터링 환경 구축 나아가, api gateway와 waf 연동 나아가 amazon bedrock 가드레일 설정까지 다양한 걸 배워왔다. 이는 앞서 공부한 web llm attack에 대한 방어 환경 구축을 실제로 해볼 수 있다는 점에서 큰 의의가 있다. 사실 이걸 위해 이번 테크 캠프를 신청한 것도 있긴하다.

이번 글은 실제로 내가 저 환경을 구축해보면서, techcamp에서 했던 실시간 강의와 workshop studio에서 부족한 점을 채우고, 틀린 부분을 수정하며, 내가 실수 했던 부분들과 이번 내용을 추후에 응용해보면 좋겠다는 생각이 들어 이 글을 작성한다.

 

 

 

기본적인 환경 설정을 위해 일단, cloudformation을 활용해야한다. 그러기 위해선 위 techcamp의 2-2. aws 계정으로 시작 탭을 눌러 밑으로 내리다 보면, 관련된 template 이 있을 것이고, 그걸 다운 받은 다음, 위 그림에서 보이는 것처럼 cloud stack 생성 시, template file을 업로드 하라고 나오는데, 이때 방금 다운 받은 tamplate 파일을 올리면 된다. 참고로 파일 이름은 presetup.yaml 이다. 만약 링크안에 파일을 다운로드할 수 없다면, 아래 파일을 다운받으면 된다.

presetup.yaml
0.00MB

 

stack name은 presetup으로 해야하고, 밑에 github 은 그대로 둬야한다. 물론 studio 설명대로 하는 것이 당연 좋지만, 가끔 저런 이름을 다른 고유의 이름으로 설정하는 사람이 있을 것이다. 내가 그랬다. 그렇게 stack name을 presetup이 아닌, 다른 이름( SJM-GenAI-Security-Architecture )을 기입하다보니 

 

cdktoolkit과 bedrock-saas 스택이 15분이 지나도 나타나지 않았다. 위 그림을 보면, 저 생성 시작 시간을 보면, 00시 37분에 시작했는데, 컴퓨터 오른쪽 아래 시간은 00시 54분이다. 즉, 17분이 지났는데도 cdktoolkit과 bedrock-saas 스택이 생성되지않았다.

 

위 그림은 stack name을 presetup, 즉, workstation에서 나오는 설명 그대로 이행했을 때 나오는 화면이다. 따라하닌까 15분도 안걸리고, 저렇게 cdktoolkit과 bedrock-saas stack 생성되는 것을 볼 수 있었다.

 

레퍼지토리도 그대로 둬야 하는 이유는 스택 생성에 필요한 필수 소스 코드구성 파일을 포함하고 있기 때문이다. 

CDK 애플리케이션 코드: API Gateway, Lambda, DynamoDB, Amazon Bedrock, SageMaker 등과 같은 리소스를 정의하는 CDK 스크립트
구성 파일 (configs.json): CodeBuild의 BuildSpec에서 configs.json 파일을 수정하는 단계가 있습니다. 이 파일은 스택 접두사(STACK_PREFIX)와 같은 설정을 정의하며, 다중 테넌트 환경을 지원하기 위한 설정

 

의존성 파일 (requirements.txt): Python 의존성을 설치하는 데 사용됩니다. 이는 CDK 애플리케이션이 실행되기 위해 필요한 패키지를 정의

 

테스트 및 개발 환경: SageMaker 노트북 인스턴스에서도 이 리포지토리를 기본 코드 저장소로 사용하므로, 개발자가 리포지토리의 코드를 활용해 테스트하거나 수정할 수 있음.
 
그 외 다양한 이유로 해당 레퍼지토리를 사용해야하기에 저것은 그대로 두고 next 를 누른다.
 
 
더 나아가
 
The resource EC2Instance is in a CREATE_FAILED state This AWS::EC2::Instance resource is in a CREATE_FAILED state. Resource handler returned message: "The image id '[ami-0bfddf4206f1fa7b9]' does not exist (Service: Ec2, Status Code: 400, Request ID: dd0c38d5-745f-4b1a-9c91-f37166415f9e) (SDK Attempt Count: 1)" (RequestToken: c1d70d88-d690-5a02-a741-bba0ef825f7a, HandlerErrorCode: InvalidRequest)

 

환경 구성시 무조건 us 오리곤 주로 하는 것을 잊지말자. 왜냐면, stack 생성시, 위 오류처럼 EC2Instance 리소스가 CREATE_FAILED 상태에 빠지게 된다.  알고보니, 

 

해당 ec2 ami는 us-west-2에만 있는 ami 이기 때문이다. 그러니 꼭 환경구성시 오리곤 주에서 하자... 왜냐면 내가 괜히 설명 제대로 안 듣고, 다른 주에서 하다가 저런 에러를 봤기때문이다.

 

그림 1
그림 2

 

전반적인 환경 세팅이 끝나면, 그림 1에서의 왼쪽 탭처럼 3개의 스택이 create_complete 라고 나올 것이고, 정상 작동 하는지 확인하기위해 bedrock-saas stack 탭을 클릭하면, api gateway url이 나올텐데, 저걸 클릭하면, 그림 2처럼 인증 토큰이 필요하다는 것을 볼 수 있다. 그렇게 전반적인 환경 세팅은 마쳤다.

 

 

bedrock 서비스에 들어가서 모든 모델에 대한 엑세스 권한도 허용으로 설정해주자

 

 

초기 설정이 끝난 뒤, vpc 엔드포인트 목록을 살펴보니, 저렇게 엔드포인트 세팅이 끝났음을 확인할 수 있었다. 이에 따라 lambda 서비스는 위 서비스들을 퍼블릭 노출 없이 endpoint들을 통신할 수 있게 된 걸 알 수 있다.

 

즉, 저 빨간 색 프라이빗 링크 부분이다. 저 프라이빗 링크 설정을 저 위에 엔드포인트 설정으로 했다는 것이고, 

그림3

 

그림4

그림 3의 해당 lambda 함수의 구성을 보면, 소속 vpc가 그림 4의 endpoint 들의 vpc들과 같은 공간에 세팅되어있을 확인할 수 있다.  

 

그러면 세팅된 배드락 api gateway를 테스트해보자.

api gateway 서비스에 접속하여, api 키 탭에 가보면, api 키를 복사할 수 있다.

 

그런다음, 배드록 서비스에 request를 보낼 url 경로이다. 즉 저기 경로를 통해 aws bedrock 서비스에다 묻고 싶은 내용을 물어볼 수 있다는 것이다.

 

ex)

export API_TOKEN="3d0xydR1rbeWEBB3ZMgkaUGCPWh9K9r2aXfI5AJc"

export FM_ENDPOINT="https://cbotfgloal.execute-api.us-west-2.amazonaws.com/s89a29ec201_prod"

 

이를 위해선, api 를 호출해야하고, 방금 위에서 복사한 api key와 url endpoint를 cloudshell에서의 변수( API_TOKEN, FM_ENDPOINT ) 값으로 설정해준다.

 

그림 5
그림 6

그림 5에서처럼 저렇게 변수를 설정해둔 다음, 그 변수를 이용하여 그림 6에서처럼 api를 호출하여 질문을 해보았다. 처음 질문은 what is Amazon Bedrock?(아마존 배드락 뭐임?), what is apple?(사과가 뭐임?) 이렇게 함 물어보았고, 그에 따라 적절한 답변들을 얻을 수 있었다. 즉 정상 작동이 된다는 걸 확인하였다.

 

즉, 이는 위 그림에서 빨간색 네모 박스 쳐진 부분, 이 부분의 흐름이라고 보면 되겠다. 내가 계속 앞에서 아키텍쳐를 따와서 아키텍쳐의 어느 부분의 흐름을 빨간색 네모로 치면서 알려주려는 이유는 그래야 지금 현재 하고 있는게 정확히 아키텍쳐에 어느 부분에 해당하는지를 알 수 있고, 이는 곧 전체적인 흐름도를 알 수 있기 해주기 때문이다. workshop studio에는 이런 설명이 필요없어서 그런지, 초반에만 보여주고, 다른 페이지에는 언급이 없어서 이 부분이 답답했다. 그래서 우리가 실습하고 있는지를 전체적인 아케텍쳐 파트에서 알고자 저렇게 빨간색 네모박스로 표시해보았다.

 

 

dynamodb의 bedrock-saas-logsdynamodbstacklogsdynamodbstacklogs 라는 테이블에 들어가면 amazon bedrock 호출 이력을 확인할 수 있다. 방금 2번 질문했는데, 왜 이력은 3개나 있냐고 궁금할 수 있을텐데, 저 시간이 10:26으로 나와서 저 테이블이 아닌가? 하고 한 번 더 질문해서 테스트를 해봤기 때문이다. 그러나 시간은 왜 안 맞는지 이해가 안간다. 미국 오리건 주 시간인지 확인해보았다. 오전 3시에서 4시 사이인 시간이라 큰 상관이 없었다. 

 

 

암튼 이 부분은 위 그림의 빨간색 동그라미 친 부분이다. 즉, api 호출이 lambda를 트리거하면, lambda는 배드록 서비스에 관련된 질문을 보내는 것과 dynamo db에 api 호출을 이력으로 남기게 되는 것이다. 

 

 

그런 다음, 테넌트 권한 관리를 위한 테이블을 생성할 것이다. 즉, 쉽게 말하면, 베드록 서비스에 관한 특정 요청에 대한 사용자 인증에 대해 테이블을 만드는 것이다. 그럼 뭘 위한 인증 체계냐 그건 뒤에서 설명하겠다. 일단, 테이블 초기 설정은 저렇게 설정하면 되고,

테이블에서 항목 생성에서 pk은 TEAM#tenant4, sk는 META로 설정하였다. pk에서 값으로 TEAM#tenant는 TEAM이라는 키워드를 통해 tenant4 라는 키워드의 팀 데이터를 하나의 파티션으로 그룹화시킬 수 있다. 그리고 그렇게 그룹화된 데이터 중 세부적으로 더 검색해보거나 정렬하기위해 SK 라는 정렬키 값을 META로 지정해둔 것이다.

 

허용 AI 모델은 amazon.titan-text-express-v1 으로 설정하였고,  API_KEYS 값은 방금 위에서 본 배드락 api gate의 api의 키 값을 설정하였다. 즉, tenant4 라는 팀 id 를 가진 사람이 amazon.titan-text-express-v1 모델에 방금 테이블에서 설정한 api 키를 들고, 요청을 해야지만 인증이되서 통과할 수 있는 것이다. 즉, 이는 인증을 위한 테이블이고, 해당 테이블을 이용하여 인증 기능을 해줄 람다 함수는 밑에 이어서 작성한다.

 

참고로 API_KEYS에는 api gate의 api키 값을 넣어야한다. api 키의 ID를 넣는 것이 아니다. workshop studio 에서는 api id를 넣으라고 나오는데, 이러면 이따 테스트에서 계속 실패한다는 문구가 나올 것이다. 왜냐하면 요청 헤더의 x-api-key의 값을 api id가 아닌, api 값 자체를 그대로 쓰고 테스트할 거기 때문이다. 이렇게 되면 람다 함수는 요청 헤더에서  x-api-key의 값을 api id가 아닌, api 값 자체를 가져올테고, 이거를 dynamodb에 있는 저 API_KEYS의 값과 비교할텐데, 만약 저 값이 api 값이 아닌, api id가 들어간다면? 일치하지 않다면서 계속 테스트에 실패할 것이다.

 

위 그림을 보면 API 키 값과 ID가 엄연히 다른 것을 알 수 있을 것이다.  

이거 때문에 몇몇 분들과 그때 강의 해주신 강사님까지 전부 테스트에 실패했었다. 물론 이때 나는 처음 저 DB 테이블 이름부터 잘못지어서 여기까지 가지도 못했지만, 지금은 이렇게 제대로 알게되어서 다행이라는 생각이 든다.

 

 

참고로 저 테이블 이름이라던지, PK, SK, 모델은 다 똑같이 입력해야 람다가 해당 테이블에 접근해서 조회가 가능하닌까 꼭 똑같이 따라하자. 안 그러면 람다함수를 고치든, DB 테이블을 다시 작성해서 권한 연결을 하든... 암튼 간단한 실습을 하고 싶을텐데 매우 귀찮아진다. 왜냐면,내가 저 DB 테이블 이름을 saas_permission 이라고 지어서 저 테이블 작성부터 다시 했었다... 이름은 못 바꾸니, 걍 직접 람다 코드의 참조할 DB 테이블을 수정했었다..

 

import { DynamoDBClient, GetItemCommand } from "@aws-sdk/client-dynamodb";

const dynamoDBClient = new DynamoDBClient({});

export const handler = async (event, context, callback) => {
    console.log('Received event:', JSON.stringify(event, null, 2));

    // 요청 파라미터 추출
    const { headers, requestContext, queryStringParameters } = event;
    const apiKey = requestContext.identity.apiKeyId;
    const teamId = headers['team_id'];
    const modelId = queryStringParameters.model_id;

    try {
        // DynamoDB에서 팀 정보 가져오기
        const teamParams = {
            TableName: 'saas-permission',
            Key: {
                PK: { S: `TEAM#${teamId}` },
                SK: { S: 'META' }
            }
        };
        const teamData = await dynamoDBClient.send(new GetItemCommand(teamParams));

        if (!teamData.Item) {
            return generateDeny('Unauthorized');
        }

        // 허용된 모델 ID와 API 키 확인
        const { ALLOWED_MODELS, API_KEYS } = teamData.Item;
        if (ALLOWED_MODELS?.SS?.includes(modelId) && API_KEYS?.SS?.includes(apiKey)) {
            return generateAllow('authorized-user', event.methodArn);
        } else {
            return generateDeny('Unauthorized');
        }
    } catch (err) {
        console.error(err);
        return generateDeny('Unauthorized');
    }
};

// IAM 정책 생성 헬퍼 함수
const generatePolicy = (principalId, effect, resource) => {
    const authResponse = {
        principalId,
        policyDocument: {
            Version: '2012-10-17',
            Statement: [
                {
                    Action: 'execute-api:Invoke',
                    Effect: effect,
                    Resource: resource
                }
            ]
        }
    };
    return authResponse;
};

const generateAllow = (principalId, resource) => {
    return generatePolicy(principalId, 'Allow', resource);
};

const generateDeny = (principalId) => {
    return generatePolicy(principalId, 'Deny', '*');
};

 

이게 그 인증 코드인데, 저기서 

 

TableName 이라는 키의 saas-permission 만  saas_permission으로 바꿔주고, 추후에 iam서비스에서 lambda가 참조할 테이블의 권한 설정시 ARN 특정 부분에서 saas_permission으로 바꿔주기만 하면 되었다. 물론 매우 귀찮기에, 처음부터 그냥 똑같이 따라해서 이러한 수고를 만들지 말자. 물론 저 API_KEYS 부분만은 앞서 말한 것처럼 본인의 bedrock api gateway api 키 값을 적어야한다는 것을 잊지 말자.

 

 

인증 헨들러 람다 함수를 만들어보면 이렇다. saas-authorizer로 이름 지은 다음, 위에서 본 코드를 복사해서 저 코드 탭을 클릭하여 코드 입력란에 붙여넣기 하면 된다.  그런 다음, 옆에 deploy를 눌러서 배포해주면, 해당 람다에 저 코드가 들어가져서 추후에 api 호출시, 저 람다 함수가 트리거될 것이다.

 

그런 다음, Lambda 서비스가 dynamo 테이블을 조회하여 값을 얻을 수 있도록 정책을 따로 만들어주었다. 이때, GetItem 선택을 선택하고,

 

리소스 위치를 특정시키고자, 리소스 리전과 테이블 네임을 ARN 지정란 기입하였다. 그렇게 정책을 만들고

 

 

그리고 인증 람다 함수의 권한에 방금 만든 정책을 연결짓기 위해 위 그림에서 처럼 정책 연결을 누르고,

방금 만든 정책을 찾아서 저렇게 체크한 다음, 권한 추가를 누르면, 해당 인증 람다 함수에 dynamo 테이블의 항목들을 조회할 수 있는 권한이 생긴 것이다. 

 

 

그런 다음, api gateway 서비스에 들어가서 bedrock api gateway에 권한 부여자를 생성해준다. 이때, 람다함수는 방금까지 우리가 만든, 인증 Lambda 함수(saas-authorizer)로 설정하면 되고, 헤더는 저렇게 설정해두자, 그러면 해당 파라미터에 맞춰 인자값만 잘 넣어주면, 람다 함수가 해당 인자 값을 받아, dynamodb 테이블에서 조회한 값하고 비교하여 일치하면 인증에 통과가 될 것이다.

 

그렇게 적절한 파라미터 값들을 헤더에 삽입하고, What is South Korea 라고 질문을 하였더니, 인증이 통과되서 그런지, 배드록 AI 로부터 답변을 얻을 수 있었다.

그림 7
그림 8

물론 api-key 값을 적지 않거나, team_id가 tenant4가 아닌, 다른 걸 적게되면, 답변을 받을 수 없었다. 즉, 사용자 인증 테스트가 성공한 것을 볼 수 있다. 이처럼, 적절하게 파라미터를 인증 인자 값으로 받아서 람다 함수가 그걸로 dynamodb 테이블의 항목과 비교하는 것을 통해 api gateway에 bedrock 서비스에 대한 사용자 인증 서비스를 구축할 수 있다.

 

이는 위 그림에서 보면, 빨간색 박스 친 부분을 하고 있던 중이었다. 

이렇게 해서 그때 실시간 강의와 저 workshop을 경험해볼 때, 부족한 점을 채우고, 틀린 부분을 수정해서 내것으로 남기고자 이렇게 블로그안에 글로 남기게되었다.

 

 

다음 포스트는 AWS WAF와 Amazon Bedrock 가드레일을 통해 보안을 더 강화하고자 하는 글을 남기고자한다.