
# 서명 키 교체

`POST /v1/signing-keys:rotate` · 권한 `signing-keys:write`

자동 일정과 관계없이 지금 서명 키 교체를 실행합니다.

- `mode: immediate`: 정기 교체 단계를 즉시 실행합니다. 미리 게시된 `next` 키가 서명을 시작하고, 기존 `active` 키는 7일 동안 `retired` 상태로 남으며, 새 `next` 키가 게시됩니다. `next`가 활성 키와 다른 알고리즘(알고리즘 변경)을 사용하고 7일 이내에 게시된 경우 호출은 `409 next_key_too_young`와 함께 실패합니다.
- `mode: emergency`(키 유출 의심): 기존 `active` 키가 폐기되어 JWKS에서 **즉시** 제거됩니다. `next`가 활성화되고 새 `next`가 생성됩니다. `revoke_next: true`는 `next`도 폐기하고, 새로 생성되어 한 번도 게시되지 않은 키를 활성화합니다. `revoke_sessions: true`는 환경의 모든 세션과 리프레시 토큰 패밀리도 폐기하므로 사용자는 다시 로그인해야 합니다. `reason`가 필요합니다.

## 연합 키

`use`는 환경의 연합 키 중 하나를 지정할 수도 있습니다: `saml_sp_signing`와 `saml_sp_encryption`(SP 메타데이터에 게시되는 SAML 서비스 제공자 서명 및 암호화 키), 또는 `oidc_rp_signing`(엔터프라이즈 OIDC 연결의 `private_key_jwt` 키, 게시 위치 `/sso/jwks.json`). 이 키들은 항상 RSA-3072이며 1년에 한 번 자동으로 교체됩니다. 새 `next` 키는 인계 30일 전에 게시되고, 기존 키는 인계 후 30일 동안 게시 상태로 남습니다.

- `mode: immediate`는 미리 게시된 `next` 키를 지금 활성화합니다. 기존 키는 30일 더 게시 상태로 남습니다(`retired_kids`에 표시됨). 게시된 `next` 키가 없으면 호출은 `409 conflict`와 함께 실패하며, `next` 키가 7일 이내에 게시되었다면 `409 next_key_too_young`와 함께 실패합니다.
- `mode: emergency`는 활성 키를 메타데이터 또는 JWKS에서 **즉시** 제거하고 개인 키를 파기합니다(`revoked_kids`). 게시된 `next` 키가 활성화되며, 없거나 `revoke_next: true`를 사용한 경우 새로 생성되어 한 번도 게시되지 않은 키가 활성화됩니다. URL에서 메타데이터 또는 JWKS를 불러오는 ID 공급자는 다음 갱신 때 변경 사항을 반영합니다. 메타데이터나 인증서를 직접 업로드한 공급자는 새 인증서를 업로드할 때까지 로그인을 거부합니다. 이 경우 영향을 받는 연결마다 `connection.sp_certificate_rotation_started` 웹훅 이벤트가 전송됩니다.

연합 키의 경우 응답의 `next_kid`은 `null`이고, `removed_kids`는 비어 있으며, `revoke_sessions`은 허용되지 않습니다. 환경에서 SAML이나 `private_key_jwt`를 한 번도 사용하지 않았다면 교체할 키가 없으므로 호출은 `409 conflict`와 함께 실패합니다.

### 연결 키

SAML 연결은 환경 키 대신 전용 SP 키를 가질 수 있습니다. 이 키를 교체하려면 `connection_id`(`conn_…`)를 `use: saml_sp_signing` 또는 `saml_sp_encryption`와 함께 추가합니다. 같은 모드, 권한 및 규칙이 적용되지만 해당 연결의 키 세트만 변경됩니다. 환경의 키와 다른 연결의 키는 그대로 유지되며, `connection.sp_certificate_rotation_started` 이벤트는 해당 연결에 대해서만 전송됩니다. 감사 기록의 대상은 환경이며, 연결은 `metadata.connection_id`에 기록됩니다. 알 수 없는 연결이나 다른 환경의 연결은 `404 not_found`와 함께 실패합니다. 환경의 키를 사용하는 연결은 `409 connection_keys_not_dedicated`와 함께 실패합니다(환경 키를 교체하려면 `connection_id`를 빼고 요청하십시오). `connection_id`가 없으면 환경의 키 세트가 교체됩니다.

긴급 모드에서는 `signing-keys:write` 외에 자격 증명에 명시적으로 부여된 `signing-keys:emergency` 권한이 필요합니다. 해당 권한이 있는 제한된 키 또는 M2M 클라이언트만 호출할 수 있습니다. 전체 액세스 비밀 키(`idp_sk_…`)는 긴급 모드에서 `403 insufficient_permissions`를 받습니다.

새 토큰 서명 키는 환경 정책의 알고리즘을 사용합니다([정책 조회](/docs/api/management/signing-keys/get-policy)). 토큰 서명 키 교체가 성공하면 `signing_key.rotated` 또는 `signing_key.revoked` 웹훅 이벤트가 전송되며, 모든 교체는 관리 감사 기록을 남깁니다. 이 엔드포인트는 "키 및 비밀 발급" 속도 제한 그룹에 속합니다. `Idempotency-Key`를 보내십시오. 네트워크 오류 후 재시도하면 두 번 교체하지 않고 첫 번째 결과를 반환합니다.

## 요청

JSON 본문(알 수 없는 필드는 `400 unknown_field`와 함께 거부됩니다):

| 필드 | 타입 | 필수 | 설명 |
|---|---|---|---|
| `Idempotency-Key` | 헤더 | 아니요 | 권장합니다. `A-Z a-z 0-9 _ -`의 16-64자입니다. |
| `mode` | 문자열 | 예 | `immediate` 또는 `emergency`. |
| `use` | 문자열 | 아니요 | 교체할 키입니다. 기본값은 `token_signing`(액세스 및 ID 토큰 서명 키)입니다. 연합 키는 `saml_sp_signing`, `saml_sp_encryption`, `oidc_rp_signing`입니다([연합 키](#연합-키) 참조). |
| `revoke_sessions` | 불리언 | 아니요 | 긴급 및 `token_signing` 모드에서만 사용합니다. 모든 세션과 리프레시 토큰도 폐기합니다. 기본값은 `false`입니다. |
| `revoke_next` | 불리언 | 아니요 | 긴급 모드에서만 사용합니다. 게시된 `next` 키도 폐기합니다. 기본값은 `false`입니다. |
| `reason` | 문자열 | 긴급 모드: 예 | 긴급 모드에서만 사용합니다. 출력 가능한 ASCII 문자 1-512자이며, 예를 들어 사고 번호를 적습니다. 비밀이나 개인 정보는 입력하지 마십시오. |
| `connection_id` | 문자열 | 아니요 | `saml_sp_signing` 및 `saml_sp_encryption` 모드에서만 사용합니다. 전용 SP 키를 가진 SAML 연결(`conn_…`)입니다. 환경 키 대신 이 연결의 키 세트가 교체됩니다([연결 키](#연결-키) 참조). |

`mode: immediate`를 사용하여 `revoke_sessions: true`, `revoke_next: true` 또는 `reason`를 보내면 `422`와 함께 실패합니다(`not_allowed`). `connection_id`가 `token_signing` 또는 `oidc_rp_signing`와 함께 있으면 같은 방식으로 실패합니다.

## 응답

교체 결과를 담은 `200 OK`를 반환합니다. `activated_kid`는 이후 서명에 사용되는 키이고, `next_kid`는 새로 게시된 `next` 키입니다(연합 키의 경우 `null`). `retired_kids`은 서명을 멈추었지만 계속 게시되어 있는 키입니다(토큰 서명 키는 7일, 연합 키는 30일). `revoked_kids`은 긴급 교체로 JWKS에서 제거된 키입니다. `removed_kids`은 7일 기간이 끝나 같은 단계에서 JWKS를 떠난 폐기 키입니다.

```json
{
  "mode": "immediate",
  "use": "token_signing",
  "activated_kid": "ypeBEsobvcr6wjGzmiPcTaeG7_gUfE5yuYB3ha_uSLs",
  "next_kid": "GKw-c0PwFokMUQ6T-TUmEWnZ4_VlQ2Qpgw-vCTT0-OQ",
  "retired_kids": ["PiPoFgA5WUoziU9lZOGxNIu9egCI1CxKy3PurtWcAJ0"],
  "revoked_kids": [],
  "removed_kids": ["Ln0sA6lQeuJl7PW1NWiFpTOTogKdJBOUmXJloaJa78Y"],
  "revoke_sessions": false
}
```

## 오류

| HTTP | 코드 | 발생 조건 |
|---|---|---|
| 400 | `unknown_field` | 본문에 알 수 없는 필드가 있습니다. |
| 400 | `malformed_request` | 본문이 올바른 JSON이 아니거나 `Idempotency-Key` 헤더가 잘못되었거나 중복되었습니다. |
| 403 | `insufficient_permissions` | 자격 증명에 `signing-keys:write`가 없거나, 명시적으로 부여된 `signing-keys:emergency` 없이 긴급 모드가 요청되었습니다. |
| 404 | `not_found` | `connection_id`가 이 환경의 SAML 연결을 가리키지 않습니다. |
| 409 | `connection_keys_not_dedicated` | `connection_id`가 환경의 SP 키를 사용하는 연결을 가리킵니다. |
| 409 | `next_key_too_young` | 알고리즘 변경이 대기 중이고 `next` 키가 7일 이내에 게시되었거나, (연합 키, `immediate`) `next` 키가 7일 이내에 게시되었습니다. |
| 409 | `conflict` | 환경에 게시된 `next` 키가 없거나, 연합 `use`의 활성 키가 없거나, 이전 연합 키가 아직 30일 중첩 기간 중입니다. |
| 415 | `unsupported_media_type` | 콘텐츠 타입이 `application/json`가 아닙니다. |
| 422 | `validation_failed` | 필드가 없거나 잘못되었습니다. 포인터가 해당 필드를 가리킵니다(예: `/reason`). |
| 422 | `idempotency_key_reuse` | `Idempotency-Key`가 다른 본문과 함께 이미 사용되었습니다. |
| 501 | `not_implemented` | 이 배포에서는 아직 사용할 수 없는 작업입니다. |
| 401 | `unauthenticated` | 자격 증명이 없거나, 잘못되었거나, 폐기되었습니다. |
| 429 | `rate_limited` | 속도 제한을 초과했습니다. `Retry-After` 기간 이후에 다시 시도하십시오. |
| 503 | `service_unavailable` | 종속 서비스를 일시적으로 사용할 수 없습니다. `Retry-After` 기간 이후에 다시 시도하십시오. |

## 예시

정기 교체:

```bash
curl -X POST "https://api.eu.keenauth.com/v1/signing-keys:rotate" \
  -H "Authorization: Bearer $KEENAUTH_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"mode":"immediate"}'
```

`signing-keys:write`와 `signing-keys:emergency`를 부여한 제한된 키로 긴급 교체:

```bash
curl -X POST "https://api.eu.keenauth.com/v1/signing-keys:rotate" \
  -H "Authorization: Bearer $KEENAUTH_EMERGENCY_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"mode":"emergency","revoke_sessions":true,"reason":"INC-2041 key exposure"}'
```

게시된 `next` 키도 폐기하면서 SAML SP 서명 키를 긴급 교체:

```bash
curl -X POST "https://api.eu.keenauth.com/v1/signing-keys:rotate" \
  -H "Authorization: Bearer $KEENAUTH_EMERGENCY_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"mode":"emergency","use":"saml_sp_signing","revoke_next":true,"reason":"INC-2042 SP key exposure"}'
```

한 연결의 전용 SAML SP 서명 키를 긴급 교체:

```bash
curl -X POST "https://api.eu.keenauth.com/v1/signing-keys:rotate" \
  -H "Authorization: Bearer $KEENAUTH_EMERGENCY_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"mode":"emergency","use":"saml_sp_signing","connection_id":"conn_01h2xcejqtf2nbrexx3vqjhp41","reason":"INC-2043 connection key exposure"}'
```
