API

애플리케이션 간에 데이터를 주고받거나 기능을 사용할 수 있도록 하는 인터페이스
클라이언트가 서버의 특정 엔드포인트로 요청을 보내면, 서버는 JSON 형태로 응답을 반환한다.
API Key는 요청을 누가 보냈는지를 식별하는 비밀문자열이다.
인증수단이나 key단위로 role을 분리 (업로드,리포트 제출 등 가능 등)하여 사용한다.
# 반면 OAuth2는 해당 앱이 제한시간동안 허용된 행동을 토큰을 통해 API 호출을 한다. (Secret은 접근제어나 감사 로그 등의 기능을 하는 key vault나 secrets manager 등으로 관리 필요)
1) 엔드포인트
GET https://api.example.com/v1/alerts/{id}
먼저 요청할 엔드포인트가 존재
(api.openai.com/v1... , https://www.virustotal.com/api/v3... 등)
www.frontend.com/api... 와 같이 프론트엔드에 요청하면 백엔드 내부에서 외부 API를 호출할 수 있으며 당연한 것이지만 외부 API 문서에서 나와있는 형식은 백엔드에서 외부 API를 호출할 때만 지키면 된다.
내부든 외부든 서버의 기능(함수등)을 호출하는 경로가 있다면 엔드포인트이다.
엔드포인트에 요청할 때는 {id} 같은 Path Params를 지정해놓는 것을 볼 수 있다.
(API에 따라 없어도 충분히 동작할 수 있으며 query parameter가 조건에 대한 것이면 path parmaeter는 대상에 대한 것이다.)
2) 요청 파라미터
1) GET Method 시
Query Params (requests에서는 params) 선택만으로 동작
ex) GET /rules?status=enabled&limit=50
=> 쿼리스트링 부분
2) POST Method 시
Body params(requests에서는 json) 생성/액션 대부분은 Body 필요.
ex) payload 부분 (request body)
import requests
url = "https://www.virustotal.com/api/v3/ip_addresses/8.8.8.8/comments"
payload = {
"data": {
"type": "comment",
"attributes": {
"text": "보안 테스트용 코멘트입니다."
}
}
}
headers = {
"x-apikey": "YOUR_API_KEY",
"accept": "application/json",
"content-type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.status_code)
다만 서버가 추가 데이터가 필요 없는 액션으로 설계했다면 Body 없이도 허용.
ex) POST /incidents/{id}/comments
Post는 보통 데이터 생성+전송이고 가끔 행동(Action)’을 트리거하는 경우도 있다.(어떤 기능을 실행)
참고 1) requests 예시
def get(url, params=None, headers=None, **kwargs): ...
def post(url, data=None, json=None, headers=None, **kwargs): ...
| Positional Argument | 함수 정의에서 앞에 있는 순서대로 매칭 | requests.get("https://...") |
| Keyword Argument | 이름을 분명히 지정해야 함 | requests.get(url, params={...}) |
positional argument는 위치 즉 순서를 맞춰야 하고
순서대로 전달해도 의미가 명확하기 때문에 '=' 이런식으로 안써도 되고 기본값이 없으므로 필수이다.
다만 Keyword로도 전달 가능 이때는 '=' 써야됨.
인자가 여러 개 생기면 순서 헷갈리니까 뒤쪽(옵션/선택 인자)은 keyword로 쓰는 게 가장 흔하다.
인자의 변화에 대비하는 함수의 생존 전략이 바로 가변 인자다.
- *args: 위치 인자 가변 수
- **kwargs: 키워드 인자 가변 수
참고 2) 보안 관련 조회 API 문서
IP 분석 정보 조회, 코멘트 등록 등 API 호출 단위를 각각 함수형태로 캡슐화하여
독립적인 모듈로 구성하고, 실행부에서 해당 기능들을 호출하여 자동화한다.
Quickstart/Examples => 쓰려는 기능 스캔
https://docs.virustotal.com/reference/overview
VirusTotal API v3 Overview
🚧 Commonly missed: Looking for more API quota and additional threat context? Contact us to learn more about our offerings for professionals and try out the VT ENTERPRISE Threat Intelligence Suite. Looking for your VirusTotal API key? Jump to your person
docs.virustotal.com
https://www.abuseipdb.com/api.html
API Documentation - AbuseIPDB
APIv1 is deprecated in favor of APIv2. The sunset date is 2020-02-01. Support tickets for APIv1 will not receive responses, except under special circumstances. This page will remain here for archival purposes. AbuseIPDB provides a free API for reporting an
www.abuseipdb.com
이외에 방화벽 API 등 조치 API는 Soar 등의 플랫폼에서 사용되어 특정 언어가 아닌 HTTP 요청(Method+URL+Header+body) 기준으로 기능 설명되어 있음.
참고:
2️⃣ 실패 유형별로 try/except를 다르게 써야 한다
아래 표를 코드 수준으로 연결해보자.
실패 유형중단 여부try/except 전략
| 인증 실패 (401/403) | 중단 | 즉시 raise |
| 쿼터 초과 (429) | 계속 | 대기 후 재시도 |
| 서버 장애 (5xx) | 계속 | 재시도 |
| 입력 오류 (400) | 계속 or 중단 | 로직 수정 |
| 네트워크 오류 | 계속 | 재시도 |
👉 이 차이를 코드로 표현할 수 있어야 한다.
3️⃣ 예시 1: 인증 실패는 즉시 중단 (raise)
import requests
def get_ip_reputation(ip, headers):
url = f"https://api.example.com/ip/{ip}"
r = requests.get(url, headers=headers, timeout=5)
if r.status_code in (401, 403):
# 인증 문제 → 전체 자동화 중단
raise RuntimeError("API authentication failed")
r.raise_for_status()
return r.json()
왜 try/except로 감싸지 않았나?
- 인증 실패는 다음 이벤트로 넘어가면 안 되는 오류
- 토큰 만료 / 권한 문제는 즉시 알려야 함
👉 이게 “중단 기준을 아는 코드”다.
4️⃣ 예시 2: Rate Limit (429)은 try/except로 흡수
import time
import requests
def get_ip_reputation(ip, headers):
url = f"https://api.example.com/ip/{ip}"
for attempt in range(3):
try:
r = requests.get(url, headers=headers, timeout=5)
if r.status_code == 429:
time.sleep(10)
continue
r.raise_for_status()
return r.json()
except requests.exceptions.Timeout:
time.sleep(2)
return None
여기서 핵심 포인트
- 실패했지만 자동화를 멈추지 않는다
- 다음 IP / 다음 이벤트로 넘어갈 수 있음
👉 SOAR에서 가장 흔한 패턴이다.
5️⃣ 예시 3: 서버 장애 (5xx)와 네트워크 오류
try:
r = requests.get(url, headers=headers, timeout=5)
r.raise_for_status()
except requests.exceptions.Timeout:
# 네트워크 불안정 → 재시도 대상
retry()
except requests.exceptions.HTTPError:
if 500 <= r.status_code < 600:
retry()
else:
raise
이 코드가 말하는 것
- 5xx = “내 잘못 아님” → 재시도
- 4xx = “내 잘못” → 중단 또는 수정
6️⃣ 예시 4: JSON은 항상 깨진다 → try/except + get()
def parse_score(data):
try:
return data["data"]["attributes"]["reputation"]
except (KeyError, TypeError):
return None
또는 더 안전하게:
def parse_score(data):
return (
data.get("data", {})
.get("attributes", {})
.get("reputation")
)
👉 이건 문법 문제가 아니라 운영 안정성 문제다.
7️⃣ 정리하면 이렇게 말하면 된다 (설명용 문장)
“보안 자동화에서 try/except는
API 실패를 숨기기 위한 것이 아니라
실패 유형에 따라 중단·재시도·무시를 결정하기 위한 도구다.
인증 실패는 즉시 중단하고,
쿼터·네트워크·서버 오류는 재시도 대상으로 처리한다.”
예외처리 시 알아내는 루
| KeyError | dict에 없는 키 접근 |
| IndexError | 리스트 인덱스 초과 |
| TypeError | 타입 안 맞음 (연산안됨) |
| ValueError | 값은 타입 맞는데 의미가 틀림 |
traceback ~( 클래스이름 뽑):
print(dir(requests.exceptions))
raise_for_status() 는 서버는 살아있지만 리소스가 없는 404 같은 에러를 실패로 하여 틀린 판단을 막음
'basic > tutorial' 카테고리의 다른 글
| Github 기본 (0) | 2024.09.25 |
|---|---|
| Python Flask 웹 서버 실행 (0) | 2024.08.26 |