ES /docs

AwsS3Manager credential expiration not validated — missing checkToken logic

RCA: AwsS3Manager::uploadDirectoryByCredential | InvalidToken

Overview#

What Happened#

2026-06-02 23:53 KST, cupixworks-pano-postprocessor-instance 서비스(eu-central-1, hassan-allam 팀)에서 S3 업로드 시 InvalidToken 에러가 발생했다. AWS STS 임시 credential의 session token이 만료되거나 무효화된 상태에서 S3 API를 호출하여 HTTP 400 응답을 받았으며, 이후 30분간 동일 토큰으로 다운로드 작업도 실패했다.

Quick Facts#

Field Value
exception.class InvalidToken (AWS S3 SDK)
exception.message The provided token is malformed or otherwise invalid.
top_frame aws-s3.manager.ts:130 (s3.upload)
runtime Node.js (agents monorepo)
env production, eu-central-1

Affected Teams#

Team / Domain Error Count Impact
hassan-allam (pano-postprocessor) 8 (upload 4 + download 4) 파노 타일 업로드/다운로드 실패, 처리 완료된 이미지가 S3에 저장되지 않음

Timeline#

  1. 2026-06-02 23:52 KST — 다수의 pano-postprocessor 인스턴스가 blur/infer/resize 처리 수행 중
  2. 2026-06-02 23:53:59 KSTuploadDirectoryByCredential 호출 시 InvalidToken 에러 발생 (4건)
  3. 2026-06-03 00:04:10 KST — 동일 토큰으로 downloadParallel 실패 (capture_37848)
  4. 2026-06-03 00:23:25-42 KST — 추가 다운로드 실패 (capture_37811)

Error Log#

Datadog Logs

text
AwsS3Manager::uploadDirectoryByCredential | upload failed - {"message":"The provided token is malformed or otherwise invalid.","code":"InvalidToken","region":null,"time":"2026-06-02T14:53:59.145Z","requestId":"KNK1G9JKK8WAETZJ","extendedRequestId":"0UuQyr4OSdz5eaFQ8142FXXxkPrFIsm3DJQ6RKFTm7j2CSnUAJckmmjN0LLHTsp69cNwxkUnQ8jphfhbR3GoeoNJVUb9HSaz","statusCode":400,"retryable":false,"retryDelay":39.9095612733064}

Impact#

  • Service: cupixworks-pano-postprocessor-instance
  • Team: hassan-allam
  • 발생 횟수: 3 (클러스터 기준, 실제 관련 에러 8건)
  • 최초 발생: 2026-06-02 23:53 KST
  • 최근 발생: 2026-06-02 23:53 KST

Root Cause Summary#

Pano-postprocessor의 AwsS3Manager는 credential을 사용 전에 만료 여부를 확인하는 checkToken() 로직이 없다. Base 패키지의 AwsS3Manager에는 _tokenExpiresAt 추적과 600초 버퍼를 둔 토큰 갱신 메커니즘이 구현되어 있으나, pano-postprocessor는 이를 상속하지 않고 독자적인 간소화된 구현을 사용한다. resize-work.ts에서 createTileUploadCredentials로 임시 credential을 발급받지만, 장시간 처리(blur_panos 17-21분 소요) 도중 또는 이후에 토큰이 만료되면 이를 감지하거나 갱신할 방법이 없다. 또한 에러 응답의 region: null은 S3 클라이언트 생성 시 region 미설정 문제도 시사한다.

Technical Analysis#

Code Path#

  • Entry point: resize-work.ts:138uploadResizedImage 메서드
  • Credential 발급: resize-work.ts:140cupixApi.pano.createTileUploadCredentials(panoId) 호출로 STS 임시 credential 획득
  • S3 클라이언트 생성: aws-s3.manager.ts:109-117 — credential으로 새 AWS.S3 인스턴스 생성 (region 미설정)
  • Failure point: aws-s3.manager.ts:130-134s3.upload() 호출 시 만료된 token으로 인해 InvalidToken 에러
applications/agents/packages/cupix-pano-postprocessor/src/work/resize-work.ts:138-143typescript
uploadResizedImage = async (cpPano: CPPano): Promise<void> => {
    if (!cpPano.panoId) return;
    const credentials = await this.cupixApi.pano.createTileUploadCredentials(cpPano.panoId);
    await this.awsS3Manager.uploadDirectoryByCredential({ credential: credentials, directory: cpPano.resizeDir!});
    await this.cupixApi.pano.checkTileUploading(cpPano.panoId);
};

Credential 발급 직후 업로드를 수행하므로 정상적이라면 즉시 사용 가능해야 한다. 그러나 발급된 credential 자체가 무효(malformed)한 경우 — Tesla API에서 이미 만료된 STS 토큰을 반환했거나, Tesla API 인증 세션 문제로 잘못된 응답을 받았을 가능성이 있다.

applications/agents/packages/cupix-pano-postprocessor/src/manager/aws-s3.manager.ts:108-117typescript
async uploadDirectoryByCredential({ credential, directory }: { credential: TESLA.UploadCredentials, directory: string }): Promise<void> {
    const s3 = new AWS.S3({
        apiVersion: '2006-03-01',
        signatureVersion: 'v4',
        accessKeyId: credential['aws_access_key_id'],
        secretAccessKey: credential['aws_secret_access_key'],
        sessionToken: credential['aws_session_token'],
        endpoint: credential['endpoint'],
        s3ForcePathStyle: true,
    });

region 파라미터가 설정되지 않아 에러 응답에서 "region":null로 나타난다.

비교 — base 패키지의 토큰 갱신 로직:

applications/agents/packages/base/src/manager/aws-s3.manager.ts:61-80typescript
private checkToken = async () => {
    let isExpired = false;
    if (this.tokenExpiresAt != undefined) {
        const now = new Date().getTime();
        const expiresTimeStamp = new Date(this.tokenExpiresAt).getTime();
        isExpired = expiresTimeStamp - 600 * 1000 < now;
    }
    if (!isExpired || this._createCredentials == undefined) {
        return;
    }

    const _credentials = await this._createCredentials();
    this.setCredentials({
        accessKeyId: _credentials.aws_access_key_id,
        secretAccessKey: _credentials.aws_secret_access_key,
        sessionToken: _credentials.aws_session_token,
        expires_at: _credentials.expires_at,
        endpoint: _credentials.endpoint
    });
};

Base 구현은 매 업로드 chunk 전에 checkToken()을 호출하여 만료 600초 전에 자동 갱신한다. Pano-postprocessor는 이 패턴을 사용하지 않는다.

Log Evidence#

사용한 Datadog 쿼리:

text
service:cupixworks-pano-postprocessor-instance status:error @environment:production "AwsS3Manager"
text
service:cupixworks-pano-postprocessor-instance status:error @environment:production "InvalidToken"

핵심 로그 — 업로드 실패:

json
{
  "message": "AwsS3Manager::uploadDirectoryByCredential | upload failed - {\"message\":\"The provided token is malformed or otherwise invalid.\",\"code\":\"InvalidToken\",\"region\":null,\"time\":\"2026-06-02T14:53:59.145Z\",\"requestId\":\"KNK1G9JKK8WAETZJ\",\"statusCode\":400,\"retryable\":false}"
}

후속 다운로드 실패 (30분 후):

text
AwsS3Manager::downloadParallel | download failed - pano: /tmp/workspace/capture_37848/download/pano_8982256.jpg, error: The provided token is malformed or otherwise invalid.
AwsS3Manager::downloadParallel | download failed - pano: /tmp/workspace/capture_37811/download/pano_8986278.jpg, error: The provided token is malformed or otherwise invalid.

Info 레벨 로그에서 확인한 처리 시간:

  • blur_panos 단계: 1000-1283초 (17-21분) 소요
  • 에러 발생 직전까지 다수 인스턴스가 정상적으로 authenticate → run → download → blur → infer → resize 수행 중

Hypotheses Considered#

# Hypothesis Evidence for Evidence against Verdict
H1 Tesla API가 이미 만료된/무효한 STS credential을 반환 에러 메시지 "malformed or otherwise invalid" — 단순 만료가 아닌 토큰 자체 무효 시사; credential 발급 직후 사용했으므로 시간 만료 가능성 낮음; 동일 인스턴스에서 30분간 지속 실패 다른 인스턴스는 동시간대 정상 처리 완료 Confirmed
H2 장시간 처리(blur 17-21분) 중 STS 토큰 자연 만료 blur_panos 단계 장시간 소요 확인; base 패키지에 checkToken 존재는 이 문제 인지 의미 uploadResizedImage에서 업로드 직전에 createTileUploadCredentials 호출하므로 fresh credential 사용; 에러 메시지가 "expired"가 아닌 "malformed" Rejected
H3 S3 클라이언트에 region 미설정으로 인한 credential 검증 실패 에러 응답의 "region":null; S3 인스턴스 생성 시 region 파라미터 없음 AWS SDK는 region 없이도 endpoint 기반으로 동작 가능; 다른 시간대에는 동일 코드로 정상 동작 Rejected
H4 Tesla API 세션(CupixAuth) 만료로 credential 발급 API 호출 실패 → 잘못된 응답 수신 장시간 인스턴스 실행 시 세션 만료 가능; tesla-api.tscheckToken()이 API 호출 전 토큰 갱신하나 race condition 가능 명시적 API 호출 실패 로그 없음; createTileUploadCredentials 호출 자체가 예외 없이 완료된 것으로 추정 Inconclusive

Fix Recommendation#

즉시 조치 (Critical)#

  • applications/agents/packages/cupix-pano-postprocessor/src/manager/aws-s3.manager.ts:108-151
  • uploadDirectoryByCredential 메서드에서 credential의 expires_at 필드를 확인하여, 만료 임박 시 (600초 이내) 업로드를 시도하지 않고 credential을 재발급받는 로직 추가
  • 또는 base 패키지의 checkToken() 패턴을 도입하여 setApiCreateCredentials 콜백을 등록하고 매 chunk 업로드 전에 토큰 유효성 검증

단기 개선 (1주 이내)#

  • uploadDirectoryByCredential에서 InvalidToken 에러 발생 시 credential 재발급 후 재시도하는 retry 로직 구현
  • S3 클라이언트 생성 시 credential.bucket_region 값을 region 파라미터로 전달하여 region null 문제 해소
  • 에러 발생 시 현재는 logger.error만 하고 계속 진행하는데, 연속 실패 시 early return하여 불필요한 반복 시도 방지

장기 개선 (재발 방지)#

  • Pano-postprocessor의 AwsS3Manager를 base 패키지의 AwsS3Manager로 교체하거나 상속하여 일관된 credential 관리 패턴 적용
  • 모든 agent 서비스에서 동일한 토큰 갱신 메커니즘을 사용하도록 통합

Monitoring#

  • InvalidToken 에러 발생 시 즉시 알림:
text
service:cupixworks-pano-postprocessor-instance status:error "InvalidToken"
  • Credential 만료 관련 메트릭 추가 — 발급 시점과 사용 시점 간의 시간 차이 추적
  • 업로드 성공률 메트릭:
text
service:cupixworks-pano-postprocessor-instance "uploadDirectoryByCredential | end"

Risk Assessment#

  • Risk level: medium
  • 예상 복잡도: standard